<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

// namespace spica\core;

/**
 * This class provides set of methods that takes user request parameters
 * and determines which resources should be used to serve the request.
 *
 * namespace spica\core\Dispatcher
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDispatcher
{
    /**
     * The number of times that dispatcher works on a request.
     *
     * @var int
     */
    private $_dispatchedCount = 0;

    /**
     * Constructs an object of <code>SpicaDispatcher</code>.
     */
    public function __construct()
    {

    }

    /**
     * Dispatches the request to an appropriate controller.
     *
     * @throws SpicaUnresolvableRequestException when routing path (file path) can not be found and backup route is not configured;
     * @throws SpicaNotFoundControllerException when routing path (file path) does not contains conventional class;
     * @throws SpicaInfiniteRoutingException when backup route can not be found.
     * @param  array $params Keys[module, controller, action]
     */
    public function dispatch($params)
    {
        $this->addRoute($params);
        $this->_dispatchedCount++;

        $basePath   = SpicaContext::getAppPath();
        $package    = SpicaContext::getPackage();
        $module     = $params['_module'];
        $controller = $params['_controller'];
        $class      = ucfirst($package).'_'.ucfirst($module).ucfirst($controller);
        $classPath  = $basePath.'/controller/'.$package.'/'.$module.'/'.$class.'.php';

        if (false === file_exists($classPath))
        {
            return $this->backup($package, $module, $controller);
        }

        include_once $classPath;

        if (false === class_exists($class, false))
        {
            // Developer mistakes
            include_once 'library/spica/core/Exception.php';
            throw new SpicaNotFoundControllerException('Unable to load class named '.$class.' that is associated with routing path '.$package.'\\'.$module.'\\'.$controller);
        }

        $action = $params['_action'];
        $object = new $class();
        // Registers dispatched controller with application state
        $this->addController($object);

        // No such action available for the request
        if (false === method_exists($object, $method = $action.'Action'))
        {
            return $this->backup($package, $module, $controller, $action);
        }

        // Method called right before each action method. If not existed - does nothing
        if (true === method_exists($object, 'beforeInvoke'))
        {
            $object->beforeInvoke($method);
        }

        $this->addMethod($method);
        $object->{$method}();

        // Method called right after each action method. If not existed - does nothing
        if (true === method_exists($object, 'afterInvoke'))
        {
            $object->afterInvoke($method);
        }
    }

    /**
     * Adds the currently dispatched controller with application state registry.
     *
     * @internal Internally used by SpicaDispatcher#dispatch()
     * @param    object $controller Master controller
     */
    public function addController($controller)
    {
        $GLOBALS['__state__']['_controller'][] = $controller;
    }

    /**
     * Adds the currently dispatched controller's method with application state registry.
     *
     * @internal Internally used by SpicaDispatcher#dispatch()
     * @param    string $method
     */
    public function addMethod($method)
    {
        $GLOBALS['__state__']['action_method'][] = $method;
    }

    /**
     * Gets all dispatched controllers from application state registry.
     *
     * @return array
     */
    public function getDispatchedControllers()
    {
        return $GLOBALS['__state__']['_controller'];
    }

    /**
     * Gets the last dispatched controller from application state registry.
     *
     * @return object|false false if there was no active action, object otherwise.
     */
    public function getLastController()
    {
        return end($GLOBALS['__state__']['_controller']);
    }

    /**
     * Gets the executed action method of the last dispatched controller from application state registry.
     *
     * @return string|false if there was no active action, string otherwise.
     */
    public function getLastMethod()
    {
        return end($GLOBALS['__state__']['action_method']);
    }

    /**
     * Adds the currently dispatched route with application state registry.
     *
     * @internal Internally used by SpicaDispatcher#dispatch()
     * @param    array $_GET values
     */
    public function addRoute($currentGet)
    {
        $GLOBALS['__state__']['resolved_routes'][] = $currentGet;
    }

    /**
     * Gets all dispatched routes from application state registry.
     *
     * @return array
     */
    public function getRoutes()
    {
        return $GLOBALS['__state__']['resolved_routes'];
    }

    /**
     * Gets the last dispatched route from application state registry.
     *
     * @return array|false false if there was no active route, array otherwise.
     */
    public function getResolvedRoute()
    {
        return end($GLOBALS['__state__']['resolved_routes']);
    }

    /**
     * Gets the previous route from application state registry.
     *
     * @return array|false false there is not active route or there is only one route only.
     */
    public function getPreviousRoute()
    {
        $count = count($GLOBALS['__state__']['resolved_routes']);

        if (true === isset($GLOBALS['__state__']['resolved_routes'][$count - 1]))
        {
            return $GLOBALS['__state__']['resolved_routes'][$count - 1];
        }

        return false;
    }

    /**
     * Dispatches no routing request to backup one.
     *
     * Backup route is composed of $package, $module, $controller and optional $action.
     *
     * @internal Internally used by SpicaDispatcher#dispatch()
     * @throws   SpicaUnresolvableRequestException when there is no configured controller for non routing cases and debug mode is turned on.
     * @throws   SpicaInfiniteRoutingException
     * @param    string $package
     * @param    string $module
     * @param    string $controller
     * @param    string $action
     */
    public function backup($package, $module, $controller, $action = null)
    {
        $backupRoute = SpicaContext::getBackupRoute();

        if (true === empty($backupRoute))
        {
            if (false   === SpicaContext::isDebug())
            {
                $action = (!empty($action)) ? ':'.$action : ':index';
                return SpicaResponse::sendNotFound('No routing path is found for ('.$package.'\\'.$module.'\\'.$controller.$action.'). ');
            }

            include_once 'library/spica/core/Exception.php';
            $action = (!empty($action)) ? ':'.$action : ':index';
            throw new SpicaUnresolvableRequestException('Unable to resolve routing path ('.$package.'\\'.$module.'\\'.$controller.$action.'). No controller file is configured for this request URL and no backup route is specified too.');
        }

        // Possible infinite loop:
        // + first user request(1): dispatch to action
        // + action: dispatch to another (2)
        // + no route: dispatch to backup (3)
        // + not route to backup (4)
        if ($this->_dispatchedCount < 3)
        {
            // Re-dispatch the request to backup route
            return $this->dispatch($backupRoute);
        }

        include_once 'library/spica/core/Exception.php';
        $action       = (!empty($action)) ? ':'.$action : ':index';
        $backupRoute  = $package.'\\'.$module.'\\'.$controller.$action;
        $package      = SpicaContext::getPackage();
        $requestRoute = SpicaRequest::getModule().'\\'.SpicaRequest::getController().'\\'.SpicaRequest::getAction();
        throw new SpicaInfiniteRoutingException(sprintf('Infinite routing is encountered when forwarding request route (%s) to the route (%s). Backup route cannot be found. ', $requestRoute, $backupRoute));
    }

    /**
     * Gets the number of times the dispatcher executes
     *
     * @return int
     */
    public function getDispatchedCount ()
    {
        return $this->_dispatchedCount;
    }
}

/**
 * Utility that help manipulate $_SESSION array, including read-once flashes.
 *
 * A "flash" is a session value that propagates only until it is read,
 * at which time it is removed from the session.
 *
 * namespace spica\core\Session
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 12, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaSession
{
    /**
     * Session is started
     *
     * @var bool
     */
    private static $_started;

    /**
     * Is current session closed?
     *
     * @var bool
     */
    private static $_closed = false;

    /**
     * Session save handler.
     *
     * @var SpicaSessionStore
     */
    private static $_handler;

    /**
     * Session options.
     *
     * @var array
     */
    private static $_opt = array();

    /**
     * Prevents contructing an object of <code><SpicaSession</code>
     */
    private function __construct()
    {

    }

    /**
     * Starts a session.
     *
     * @return array $opt ['name', 'id', 'cookie_ttl', 'cookie_domain', 'cookie_path']
     * @return bool
     */
    public static function start($opt = array())
    {
        // start a session if one does not exist, but not if we're at the command line.
        if (true === self::isStarted())
        {
            return true;
        }

        if (isset($opt['name']))
        {
            session_name($opt['name']);
        }

        if (isset($opt['id']))
        {
            session_id($opt['id']);
        }

        // define the lifetime of the cookie
        if (isset($opt['cookie_ttl']) || isset($opt['cookie_domain']) || isset($opt['cookie_path']))
        {
            // cross subdomain validity is default behavior
            $ttl = (isset($opt['cookie_ttl'])) ? (int) $opt['cookie_ttl'] : 0;
            $domain = (isset($opt['cookie_domain'])) ? $opt['cookie_domain'] : '.'.SpicaContext::getDomain(false);
            $path = (isset($opt['cookie_path'])) ? '/'.trim($opt['cookie_path'], '/').'/' : '/';
            session_set_cookie_params($ttl, $path, $domain);
        }

        ini_set('session.use_only_cookies', 1);

        if (true === SpicaRequest::isSecure())
        {
            ini_set('session.cookie_secure', true);
        }

        self::$_started = (PHP_SAPI !== 'cli') ? session_start() : @session_start(); // PHP 5.3: returns false or true

        if (null === self::$_started)
        {
            self::$_started = ('' !== session_id());
        }

        self::$_opt = $opt;
        self::$_closed = false;
        return (bool) self::$_started;
    }

    /**
     * Do not use it for general purpose.
     * This method is used for unit testing.
     */
    public static function setStarted($started)
    {
        self::$_started = (bool) $started;
    }

    /**
     * Tests if the session is started.
     *
     * @return bool
     */
    public static function isStarted()
    {
        if (true === self::$_closed)
        {
            return false;
        }

        // in case that SpicaSession::start() is not called
        if (null === self::$_started)
        {
            return ('' !== session_id());
        }

        return self::$_started;
    }

    /**
     * Is current session closed?
     *
     * @return bool
     */
    public static function isClosed()
    {
        return (bool) self::$_closed;
    }

    /**
     * Closes the current session and starts a new session.
     *
     * @param  array $opt
     * @return bool
     */
    public static function restart($opt = array())
    {
        if (false === self::$_closed)
        {
            self::commitAndClose();
        }

        return self::start((empty($opt) ? self::$_opt : $opt));
    }

    /**
     * Returns the value or object bound with the specified name in this session,
     * or <code>null</code> if no value or object is bound under the name.
     *
     * @param  string $name Session key
     * @return string
     */
    public static function get($name)
    {
        return (true === isset($_SESSION[$name])) ? $_SESSION[$name] : null;
    }

    /**
     * Binds an value or object to this session, using the name specified.
     *
     * @param  string $name  Session key
     * @param  string $value Request value
     * @return string
     */
    public static function set($name, $value)
    {
        $_SESSION[$name] = $value;
    }

    /**
     * Checks if there is a value or object bound with the specified name in this session.
     *
     * @param  string $name Session key
     * @return bool Returns <code>true</code> if there is a value or object bound with the specified name in this session.
     */
    public static function exists($name)
    {
        return isset($_SESSION[$name]);
    }

    /**
     * Unbinds the object or value bound with the specified name in this session.
     *
     * @param  string $name Session key or segment
     * @return bool
     */
    public static function remove($name)
    {
        unset($_SESSION[$name]);
        return true;
    }

    /**
     * Returns the value or object bound with the specified name in this session,
     * or <code>null</code> if no value or object is bound under the name.
     * After the value is retrieved, it will be removed from this session.
     *
     * @param  string $name Session key
     * @return string
     */
    public static function getFlash($name)
    {
        if (true === isset($_SESSION['__flash__'][$name]))
        {
            $out = $_SESSION['__flash__'][$name];
            unset($_SESSION['__flash__'][$name]);
            return $out;
        }

        return null;
    }

    /**
     * Binds an value or object to this session, using the name specified.
     * This value is special because it is removed right after being retrieved
     * for the first time.
     *
     * @param string $name  Session key
     * @param string $value Session value
     */
    public static function setFlash($name, $value)
    {
        $_SESSION['__flash__'][$name] = $value;
    }

    /**
     * Checks a key in a certain segment exists.
     *
     * @param string $segment
     * @param mixed  $index
     * @param mixed  $value
     */
    public static function hasSegment($segment, $index)
    {
        return isset($_SESSION[$segment][$index]);
    }

    /**
     * Sets session value with value partitioning support.
     * Session array will become an 2-dimensional array.
     *
     * @param string $segment
     * @param mixed  $index
     * @param mixed  $value
     */
    public static function setSegment($segment, $index, $value)
    {
        $_SESSION[$segment][$index] = $value;
    }

    /**
     * Gets session value with value partitioning support.
     * Session array will become an 2-dimensional array.
     *
     * @param string $segment
     * @param mixed  $index
     * @param mixed  $value
     */
    public static function getSegment($segment, $index = null)
    {
        if (null === $index)
        {
            return (true === isset($_SESSION[$segment])) ? $_SESSION[$segment] : null;
        }

        return (true === isset($_SESSION[$segment][$index])) ? $_SESSION[$segment][$index] : null;
    }

    /**
     * Removes a segmented session value by its key.
     *
     * @param  string $segment
     * @return bool
     */
    public static function removeSegment($segment)
    {
        unset($_SESSION[$segment]);
        return true;
    }

    /**
     * Unbinds all object or value bound to this session.
     *
     * @return bool
     */
    public static function removeAll()
    {
        session_unset();
    }

    /**
     * Invalidates this session (delete it) and unbinds any values or objects bound to it.
     * This method can be used to log user out.
     *
     * @return bool
     */
    public static function invalidate()
    {
        session_unset();
        session_destroy();
        session_regenerate_id(true);
        return true;
    }

    /**
     * Gets the current session ID (e.x: a01vjqgvo3re5kiog962ikmbe7). If the current session is closed the session ID will be "" (empty)
     *
     * @see    SpicaSession::commitAndClose()
     * @return string Returns the session ID for the current session or the empty string ("") if there is no current session (no current session id exists).
     */
    public static function getId()
    {
        return session_id();
    }

    /**
     * Sets the current session ID.
     * Specifying an ID for setId() will always send a new cookie when
     * <code>SpicaSession::start()</code> is called, regardless if the current
     * session ID is identical to the one being set. <code>SpicaSession::setId()</code>
     * needs to be called before <code>SpicaSession::start()</code>
     *
     * @throws BadMethodCallException
     * @param  string $id
     * @return bool
     */
    public static function setId($id)
    {
        if (true === self::$_started)
        {
            throw new BadMethodCallException(__METHOD__.' must be called before session gets started.');
        }

        return session_id($id);
    }

    /**
     * Gets the current session name (e.x: PHPSESSID).
     *
     * @return string Returns the session name.
     */
    public static function getName()
    {
        return session_name();
    }

    /**
     * Sets the current session name.
     *
     * @throws BadMethodCallException
     * @param string $name
     */
    public static function setName($name)
    {
        if (true === self::$_started)
        {
            throw new BadMethodCallException(__METHOD__.' must be called before session gets started.');
        }

        session_name($name);
    }

    /**
     * Sets session save handler to handle all session operation.
     * This method is used when we try to overwrite the default session mechanism in PHP.
     *
     * @throws BadMethodCallException
     * @param SpicaSessionStore $handler @see \spica\core\session\store
     */
    public static function setHandler($handler)
    {
        if (true === self::$_started)
        {
            throw new BadMethodCallException(__METHOD__.' must be called before session gets started.');
        }

        session_set_save_handler(
            array(&$handler, 'open'),
            array(&$handler, 'close'),
            array(&$handler, 'read'),
            array(&$handler, 'write'),
            array(&$handler, 'destroy'),
            array(&$handler, 'gc')
        );

        self::$_handler = $handler;
    }

    /**
     * Counts the generated session entries.
     *
     * @return int
     */
    public static function getSessionCount()
    {
        if (null === self::$_handler)
        {
            return iterator_count(new DirectoryIterator(ini_get('session.save_path')));
        }

        return self::$_handler->getSessionCount();
    }

    /**
     * Sets the directory path where session files are stored.
     *
     * @param string $path
     */
    public function setSavePath($path)
    {
        ini_set('session.save_path', $path);
    }

    /**
     * Sets max lifetime of this session.
     *
     * @param int $seconds
     */
    public static function setLifetime($seconds)
    {
        ini_set('session.gc_maxlifetime', (int) $seconds);
    }

    /**
     * Gets max lifetime of this session.
     *
     * @return int The seconds that this session can exist before being made expired
     */
    public static function getLifetime()
    {
        return (int) ini_get('session.gc_maxlifetime');
    }

    /**
     * Sets max lifetime of the session cookie. The default behaviour for sessions
     * is to keep a session open indefinitely and only to expire a session when
     * the browser is closed.
     * This method must be called before <code>SpicaSession#start()
     *
     * @param int $seconds
     */
    public static function setCookieLifetime($seconds)
    {
        ini_set('session.cookie_lifetime', (int) $seconds);
    }

    /**
     * Gets max lifetime of the session cookie. The default behaviour for sessions
     * is to keep a session open indefinitely and only to expire a session when
     * the browser is closed. When this method returns 0, it means that session cookie
     * will not expire.
     *
     * @return int The seconds that the session cookie can exist before being made expired
     */
    public static function getCookieLifetime()
    {
        return (int) ini_get('session.cookie_lifetime');
    }

    /**
     * Writes session values or objects down to storage engine (file system or database or memory engine)
     * and close the session. The close help release lock on session entry, then allows another process
     * work on the session entry.
     *
     * We can start another session by calling session_start() without concering that session header has been sent.
     */
    public static function commitAndClose()
    {
        session_write_close();
        self::$_closed = true;
        self::$_started = false;
        self::$_handler = null;
    }

    /**
     * Destroys this session handler and closes the session.
     */
    public function __destruct()
    {
        if (false === self::$_closed)
        {
            session_write_close();
        }
    }
}

/**
 * This interface are designed to assist a developer in persisting session data into a storage system
 * otherwise the normal file system.
 *
 * @category   spica
 * @package    core
 * @subpackage session\store
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 03, 2010
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaSessionStore
{
    /**
     * Opens session
     *
     * @param string $savePath ignored
     * @param string $sessName ignored
     * @return bool
     */
    public function open($savePath, $sessName);

    /**
     * Fetches session data
     *
     * @param  string $sid
     * @return string
     */
    public function read($sid);

    /**
     * Closes session
     *
     * @return bool
     */
    public function close();

    /**
     * Updates session.
     *
     * @param  string $sid Session ID
     * @param  string $data
     * @return bool
     */
    public function write($sid, $data);

    /**
     * Destroys session provided with ID.
     *
     * @param  string $sid
     * @return bool
     */
    public function destroy($sid);

    /**
     * Garbage collection
     *
     * @param  int $sessMaxLifeTime
     * @return bool
     */
    public function gc($sessMaxLifeTime);

    /**
     * Gets total session.
     *
     * @return int
     */
    public function getSessionCount();
}

/**
 * This class represents a Ajaxified reponse message that developer
 * can use to build up a standardized Spica Ajax reponse.
 *
 * Ported from Pone_Response_AjaxContent
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      September 15, 2008
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaAjaxResponseBody
{
    /**
     * The reponse message that can be used to deliver error messages or general messages
     *
     * @var array
     */
    public $messages;

    /**
     * The flag to indicate if there is any unexpected status in the response
     *
     * @var string Values to take: '0': normal, '1' get common errors when handling, '10': fatal error
     */
    public $status;

    /**
     * The redirect URL that is often used in
     *
     * @var string
     */
    public $redirectUrl;

    /**
     * Data as an array such as rows
     *
     * @var array
     */
    public $rows;

    /**
     * Form feedbacks as an array
     *
     * @var array
     */
    public $feedback;

    /**
     * Constructs an object of <code>SpicaAjaxResponseBody</code>.
     *
     * @param string $status Values to take: '0': normal, '1' get common errors when handling, '10': fatal error
     * @param array  $messages
     * @param string $redirectUrl
     * @param array  $rows
     * @param array  $feedbacks
     */
    public function __construct($status = '0', $messages = array(), $redirectUrl = '', $rows = array(), $feedbacks = array())
    {
        $this->messages    = $messages;
        $this->status      = $status;
        $this->redirectUrl = $redirectUrl;
        $this->rows        = $rows;
        $this->feedback    = $feedbacks;
    }

    /**
     * Converts the internal content to JSON string format
     *
     * @return string
     */
    public function toJson()
    {
        return json_encode(
            array('messages'    => $this->messages,
            'status'      => $this->status,
            'redirectUrl' => $this->redirectUrl,
            'rows'        => $this->rows,
            'feedback'    => $this->feedback)
        );
    }

    /**
     * Converts the internal content to XML format.
     *
     * @return string
     */
    public function toXml()
    {
        include_once 'library/spica/core/utils/FormatUtils.php';
        return SpicaFormatUtils::arrayToXml(
            array('messages'    => $this->messages,
            'status'      => $this->status,
            'redirectUrl' => $this->redirectUrl,
            'rows'        => $this->rows,
            'feedback'    => $this->feedback)
        );
    }
}

/**
 * SpicaEventSubject provides a base class for implementing the Observer pattern.
 * This abstract class provides an interface for attaching and detaching observers.
 * Subject class also holds a private list of observers.
 *
 * The essence of this pattern is that one or more objects (called observers
 * or listeners) are registered (or register themselves) to observe an event
 * which may be raised by the observed object (the subject). (The object which
 * may raise an event generally maintains a collection of the observers.)
 *
 * namespace spica\core\EventSubject
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 25, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaEventSubject implements SplSubject
{
    /**
     * Attached subject listeners.
     *
     * @var array
     */
    protected $_listeners = array();

    /**
     * Attaches an observer to the object.
     *
     * @param SpicaEventListener $observer
     */
    public function attach(SplObserver $observer)
    {
        if (!($observer instanceof SpicaEventListener))
        {
            throw new InvalidArgumentException(sprintf('The $observer argument to %s::attach() must be derived from SplSubject. Actual: %s. ', get_class($observer), get_class($this)));
        }

        // Add a new listener
        $this->_listeners[spl_object_hash($observer)] = $observer;
    }

    /**
     * Detaches an observer from the object.
     *
     * @param SpicaEventListener $observer
     */
    public function detach(SplObserver $observer)
    {
        // Remove the listener
        unset($this->_listeners[spl_object_hash($observer)]);
    }

    /**
     * Notifies all attached observers of a new event.
     *
     * @param string|object|array $message Event name or Event object
     */
    public function notify($event = null)
    {
        foreach ($this->_listeners as $listener)
        {
            $listener->notify($event);
        }
    }
}

/**
 * A class can extend the SpicaEventListener class when it wants to be informed
 * of changes in observable objects.
 *
 * This class defines an updating interface for all observers, to receive update
 * notification from the subject. The SpicaEventListener class is used as an
 * abstract class to implement concrete observers.
 *
 * namespace spica\core\EventListener
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 25, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
abstract class SpicaEventListener implements SplObserver
{
    /**
     * Calling object
     *
     * @var SpicaEventSubject
     */
    protected $_caller;

    /**
     * Initializes a new observer and attaches the subject as the caller.
     *
     * @param SpicaEventSubject $caller
     */
    public function __construct(SplSubject $caller)
    {
        // Update the _caller
        $this->update($caller);
    }

    /**
     * Updates the observer subject with a new caller.
     *
     * This method is called whenever the observed object is changed.
     *
     * @param SpicaEventSubject $caller
     */
    public function update(SplSubject $caller)
    {
        if (!($caller instanceof SpicaEventSubject))
        {
            throw new InvalidArgumentException(sprintf('The $caller argument to %s::update() must be derived from SplSubject. Actual: %s. ', get_class($caller), get_class($this)));
        }

        // Update the caller
        $this->_caller = $caller;
    }

    /**
     * Detaches this observer from the subject.
     *
     * @return SpicaEventListener
     */
    public function remove()
    {
        // Detach this observer from the caller
        $this->_caller->detach($this);
    }

    /**
     * Notifies the observer of a new message. This function must be defined in
     * all observers and must take exactly one parameter of any type.
     *
     * @param string|object|array $message
     */
    abstract public function notify($message);
}

/**
 * Classes that implements <code>SpicaPathResolver</code> is capable of resolving
 * URLs into meaningful parts to Spica dispatcher.
 *
 * namespace spica\core\PathResolver
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 13, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaPathResolver
{
    /**
     * Connects a route name to an URL pattern.
     *
     * Additionally, attach an action to a route, and impose some
     * restrictions to route arguments.
     *
     * @param string $name A name represents a particular route.
     * @param string $type Route type: static, dynamic
     * @param string $schema Route URL pattern, e.g. 'category/:arg/*wildcard_arg'
     * @param array  $map  An array that maps package, module, controller, action
     *                     and other parameters to specific values when the route is matched
     * @param array  $requirements array of requirements for route arguments,
     *               keys are route argument names, values are regular expressions
     */
    public function addRoute($name, $type, $schema, $map, $requirements = array());

    /**
     * Updates $_GET to specify request parts, based on $url.
     *
     * The following indices of $_GET will be populated after that
     * + controller
     * + module
     * + action
     * + page
     * + other params
     *
     * Unescape/urldecode $url as neccessary before matching.
     *
     * @param  string $url
     * @return string Route name that matches with the request URL or false
     */
    public function resolve($url);

    /**
     * Sets fallback resolver, which handles the URL resolution
     * if the first resolver fails.
     *
     * @var SpicaPathResolver $resolver
     */
    public function setFallbackResolver($resolver);

    /**
     * Gets set fallback resolver, which handles the URL resolution
     * if the first resolver fails.
     *
     * @return SpicaPathResolver
     */
    public function getFallbackResolver();
}

/**
 * Instances of <code>SpicaStandardUrlResolver</code> is capable of resolving
 * URLs into meaningful parts to Spica dispatcher.
 *
 * namespace spica\core\StandardUrlResolver
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 13, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaStandardUrlResolver implements SpicaPathResolver
{
    /**
     * Profile routing config.
     *
     * @var array
     */
    protected static $_config;

    /**
     * Path to execution script file, relative to DocumentRoot of the webserver.
     * Its value is often the same as $_SERVER['SCRIPT_NAME'] for the most cases.
     * However, it is good to separate it with $_SERVER['SCRIPT_NAME'] to make unit
     * testing easier. E.x: /repos/spica/trunk/index.php where /repos is a
     * directory in DocumentRoot configured by webserver.
     *
     * @var string
     */
    protected static $_scriptPath;

    /**
     * Constructs an object of <code>SpicaStandardUrlResolver</code>.
     *
     * @param array  $config
     * @param string $scriptPath Path to script file relative to document root.
     */
    public function __construct($config, $scriptPath)
    {
        self::$_config     = $config;
        self::$_scriptPath = $scriptPath;
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/SpicaPathResolver#setFallbackResolver()
     */
    public function setFallbackResolver($resolver)
    {
        // no-op
    }

    /**
     * (non-PHPdoc)
     * @see trunk/library/spica/core/SpicaPathResolver#getFallbackResolver()
     */
    public function getFallbackResolver()
    {
        return null;
    }

    /**
     * This method is no use in this class.
     */
    public function addRoute($name, $type, $schema, $map, $requirements = array())
    {
        // no-op
    }

    /**
     * Resolves URLs into parts and populates the global $_GET.
     *
     * @param  string $url
     * @return string|false Route name that matches with the request URL or false
     */
    public function resolve($url)
    {
        $relativeUrl      = parse_url($url, PHP_URL_PATH); // has leading /

        // Removes file part and leading and trailing slash. Can be empty
        $subDirectoryPath = trim(substr(self::$_scriptPath, 0, -9), '/');

        // Remove sub directory path and / at both ends
        $relativeUrl  = trim(str_replace($subDirectoryPath, '', $relativeUrl), '/');
        $requestParts = !empty($relativeUrl) ? explode('/', $relativeUrl, 4) : array();

        // Resolve module name [a-zA-Z0-9]
        if (true === isset($requestParts[0]) && true === ctype_alnum($requestParts[0]))
        {
            $_GET['_module'] = $requestParts[0];
        }
        else
        {
            $_GET['_module'] = self::$_config['default_module'];
        }

        // Resolve controller name [a-zA-Z0-9]
        if (true === isset($requestParts[1]) && true === ctype_alnum($requestParts[1]))
        {
            $_GET['_controller'] = $requestParts[1];
        }
        else
        {
            $_GET['_controller'] = self::$_config['default_controller'];
        }

        // Resolve action name [a-zA-Z0-9]
        if (true === isset($requestParts[2]) && ctype_alnum($requestParts[2]))
        {
            $_GET['_action'] = $requestParts[2];
        }
        else
        {
            $_GET['_action'] = self::$_config['default_action'];
        }

        if (true === isset($requestParts[3]) && count($requestParts[3]) > 0)
        {
            $params    = explode('/', $requestParts[3]);

            unset($requestParts);

            for ($i    = 0, $length = count($params); $i < $length; $i++)
            {
                if ('' === trim($params[$i]))
                {
                    continue; // loop util the first real parameter is met
                }

                if (true   === isset($params[$i]))
                {
                    $key   = $params[$i];
                    $value = (true === isset($params[$i + 1]))?$params[$i + 1]:'';
                    if ('%5B%5D' == substr($key, -6))
                    {
                        $key     = str_replace('%5B%5D', '', $key);
                        $_GET[$key][] = $value;
                    }
                    else
                    {
                        $_GET[$key]   = $value;
                    }
                }

                $i++;
            }
        }

        if (false === isset($_GET['page']) || intval($_GET['page']) < 1)
        {
            $_GET['page'] = '1';
        }

        return ':default';
    }
}

/**
 * The <code>SpicaInvokable</code> interface should be implemented by any class
 * whose instances are intended to be used as lambda/closure. The class must
 * define a method of no arguments called <code>__invoke()</code>.
 *
 * namespace spica\core\Invokable
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 13, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaInvokable
{
    /**
     * Executes object as lambda function.
     */
    public function __invoke();
}

/**
 * The <code>SpicaDataFilter</code> interface should be implemented by any class
 * whose instances are used to transform text data.
 *
 * namespace spica\core\DataFilter
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 21, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaDataFilter
{
    /**
     * Transforms passed text and return filtered string.
     *
     * @param  string $text
     * @return string The filtered data.
     */
    public function doFilter($text);
}

/**
 * <code>SpicaInstructionMailbox</code> is a central repository for messages and
 * artifacts associated with an individual message.
 *
 * An instruction mailbox is used to give Spica components instructions.
 * Normally this class mailboxes are used by master controllers to give child
 * controllers guidelines on how to response to the request.
 *
 * namespace spica\core\InstructionMailbox
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 08, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaInstructionMailbox
{
    /**
     * The repository of messages.
     *
     * @var array
     */
    protected static $_mailbox = array();

    /**
     * Sends a message to a specific class or extension point in the system.
     *
     * @param string $subscriber Subscriber name: class name or extension point name.
     * @param SpicaInstructionMessage $message
     */
    public static function compose($subscriber, $message)
    {
        self::$_mailbox[$subscriber][] = $message;
    }

    /**
     * Sends a message to all relevant components, normally sub-controllers.
     *
     * @param SpicaInstructionMessage $message
     */
    public static function notifyAll($message)
    {
        self::$_mailbox['*'][] = $message;
    }

    /**
     * Checks if there is any message sent to a particular subscriber.
     *
     * @param string $subscriber
     */
    public static function hasMessage($subscriber)
    {
        if (isset(self::$_mailbox[$subscriber]) || isset(self::$_mailbox['*']))
        {
            return true;
        }
    }

    /**
     * Gets list of messages to the all subsequent components.
     *
     * @return array Empty array is returned if there is no message.
     */
    public static function getWildcardMessage()
    {
        if (isset(self::$_mailbox['*']))
        {
            return self::$_mailbox['*'];
        }

        return array();
    }

    /**
     * Gets list of messages sent to a particular components. Wildcard messages
     * will not be returned.
     *
     * @param  string $subscriber
     * @return array Empty array is returned if there is no message.
     */
    public static function getMessage($subscriber)
    {
        if (isset(self::$_mailbox[$subscriber]))
        {
            return self::$_mailbox[$subscriber];
        }

        return array();
    }
}

/**
 * <code>SpicaInstructionMessage</code> represents a message that successor software
 * component in Spica system sends to subsequent ones.
 *
 * <code>SpicaInstructionMessage</code> is controlled by <code>SpicaInstructionMailbox</code>.
 *
 * namespace spica\core\InstructionMessage
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 08, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaInstructionMessage
{
    /**
     * One and short sentence to describe about the content.
     *
     * @var string
     */
    public $summary;

    /**
     * Detailed textual content.
     *
     * @var string
     */
    public $content;

    /**
     * Validation status.
     *
     * @var int Defaults to null means undefined.
     */
    public $validationStatus;

    /**
     * The object that sends the message itself.
     *
     * @var object
     */
    public $caller;
}

/**
 * <code>SpicaActionMessage</code> represents a message that that a Spica action controller
 * compose and send to the next controller, usually a block or blockgroup that a request is forwarded to.

 * namespace spica\core
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 26, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaActionMessage
{
    /**
     * One and short sentence to describe about the content.
     *
     * @var string
     */
    public $summary;

    /**
     * Detailed textual content.
     *
     * @var string
     */
    public $details;

    /**
     * Validation status.
     *
     * @var int Defaults to null means undefined.
     */
    public $validationStatus;

    /**
     * Status code.
     *
     * @var int
     */
    public $code;

    /**
     * Constructs an object of <code>SpicaActionMessage</code>.
     *
     * @param string $summary Action message in one and short sentence
     * @param string $details Detailed message
     * @param int    $validationStatus 1 means action controller validation is passed.
     * @param int    $code 0 means no error
     */
    public function __construct($summary = null, $details = null, $validationStatus = 1, $code = 0)
    {
        $this->summary          = $summary;
        $this->details          = $details;
        $this->validationStatus = $validationStatus;
        $this->code             = $code;
    }
}

/**
 * To provide customized error handling, implement this interface and use the
 * Spica::setErrorListener() to register an instance of the implementation
 * with the Spica context.
 *
 * namespace spica\core\error
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 01, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaErrorListener
{
    /**
     * Registers a error reporter class that is responsible for handling the error
     * information. Error reporter must implements <code>SpicaErrorReporter</code>
     *
     * @param SpicaErrorReporter $reporter
     */
    public function setReporter($reporter);

    /**
     * Gets set repoter.
     *
     * @return SpicaErrorReporter
     */
    public function getReporter();

    /**
     * Hooks to receive notification of a warning/notice/recoverable error and handle it.
     *
     * Spica system can use this method to report conditions that are not serious
     * errors or fatal errors. The default behaviour is to log them and take
     * no further action.
     *
     * After invoking this method, the Spica system must continue with the processing.
     * It should still be possible for the application to process the request
     * through to the end.
     *
     * @param  int    $level Error level
     * @param  string $error Error message
     * @param  string $file  The file where error occurs
     * @param  int    $line  The line where error occurs
     * @return bool Always return true to override PHP native error handling mechanism
     */
    public function onError($level, $error, $file, $line);

    /**
     * Hooks to receive notification of a non-recoverable error and handle it.
     *
     * In PHP, fatal errors is the ones that cause scripts stop to compile or execute.
     * The Spica application should stop to provide normal processing after invoking
     * this method.
     *
     * @return bool Always return true to override PHP native error handling mechanism
     */
    public function onShutdown();

    /**
     * Hooks to receive notification of a aborted connection error and handle it.
     * When someone clicks on STOP button, this sends a RST packet and closes the connection
     *
     * @return bool Always return true to override PHP native error handling mechanism
     */
    public function onConnectionAborted();

    /**
     * Hooks to receive notification of a request timeout error and handle it.
     *
     * @return bool Always return true to override PHP native error handling mechanism
     */
    public function onConnectionTimeout();
}

/**
 * A SpicaExceptionHandler is configured in the exception.php configuration file to
 * handle a specific type of exception thrown by an SpicaContext's start() method
 *
 * namespace spica\core\error
 *
 * @see        SpicaContext::catchException
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      January 16, 2010
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaExceptionHandler
{
    /**
     * Handles the exception.
     *
     * @param Exception $e
     */
    public function process($e);
}

/**
 * If a Spica application needs to implement customized error handling, it must
 * implement this interface and then register an instance with the <code>SpicaErrorListener</code>
 * using the <code>setReporterClass</code> method. The listener will then report all errors
 * and warnings to this interface.
 *
 * namespace spica\core\error
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 04, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaErrorReporter
{
    /**
     * Reports errors occured.
     *
     * @param SpicaErrorReport $report
     */
    public function report($report);

    /**
     * Gets error message storage file that can be iterated through.
     *
     * @return Iterator
     */
    public function getStorage();
}

/**
 * Provides an error report for handling of error information.
 * This class is used by SpicaErrorReporter and SpicaErrorListener for reporting
 * and handling error information.
 *
 * namespace spica\core\error
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 04, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaErrorReport
{
    /**
     * Construct an object of <code>SpicaErrorReport</code>.
     */
    public function __construct()
    {

    }

    /**
     * Error level name.
     *
     * @var string
     */
    public $levelName;

    /**
     * Error level.
     *
     * @var int
     */
    public $level;

    /**
     * The line number of the error, or -1 if not defined.
     *
     * @var int
     */
    public $line = -1;

    /**
     * The file name of the error source.
     *
     * @var string
     */
    public $file;

    /**
     * Error trace information.
     *
     * @var string
     */
    public $trace;

    /**
     * Native error message.
     *
     * @var string
     */
    public $message;

    /**
     * User friendly error message.
     *
     * @var string
     */
    public $userMessage;
}

/**
 * Instances of this class are used to hook into SpicaDefaultErrorListener to
 * report errors that occur.
 *
 * namespace spica\core\error
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 04, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDefaultErrorReporter implements SpicaErrorReporter
{
    /**
     * Reports errors occured.
     *
     * @param SpicaErrorReport $report
     */
    public function report($report)
    {
        if (true === SpicaContext::isDebug())
        {
            $message = array(
                SpicaContext::getFullUrl(),
                ((isset($_SERVER['REFERER'])) ? $_SERVER['REFERER'] : ''),
                SpicaRequest::getRealIp(),
                $report->levelName,
                $report->message,
                $report->file.':'.$report->line,
                $report->trace
            );
            Spica::logTrace(implode(' ||| ', $message));
        }

        $message = array(
            date('Y.m.d H.i.s'),
            SpicaContext::getFullUrl(),
            ((isset($_SERVER['REFERER'])) ? $_SERVER['REFERER'] : ''),
            SpicaRequest::getRealIp(),
            $report->levelName,
            $report->message,
            $report->file.':'.$report->line
        );

        Spica::logError(implode(' ||| ', $message));
        return true;
    }

    /**
     * Gets the file that stores all message that were reported.
     *
     * @return SpicaRowList
     */
    public function getStorage()
    {
        $file = Spica::getErrorLogFile();
        return new SplFileObject($file, 'r');
    }
}

/**
 * Instances of this class are used to hook into PHP engine to override the native one,
 * and handle errors that occur.
 *
 * namespace spica\core\error
 *
 * Ported from Pone_Error
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      April 22, 2009
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDefaultErrorListener implements SpicaErrorListener
{
    /**
     * Custom error message
     *
     * @var string
     */
    private $_message;

    /**
     * Error reporter.
     *
     * @var SpicaErrorReporter
     */
    private $_reporter;

    /**
     * Configuration information
     *
     * @var array
     */
    protected $_options = array(
        'contact_email' => 'pcdinh@gmail.com',
        'page_title'    => '500 Internal Server Error',
        'template_path' => 'library/spica/core/templates/error/internal.tpl.php',
    );

    /**
     * Backtrace format in HTML
     */
    const BACKTRACE_HTML = 1;

    /**
     * Backtrace format in plain text
     */
    const BACKTRACE_TEXT = 2;

    /**
     * Constructs an object of <code>SpicaDefaultErrorListener</code> and registers methods
     * that handle recoverable errors and non-recoverable errors.
     *
     * @param array $options
     */
    public function __construct($options = array())
    {
        if (count($options) > 0)
        {
            $this->setOptions($options);
        }
    }

    /**
     * Sets options that the error listener use to give response to error events.
     *
     * @param array $options
     */
    public function setOptions($options)
    {
        foreach ($options as $key => $value)
        {
            $this->_options[$key] = $value;
        }
    }

    /**
     * Sets custom error message.
     *
     * @param  string $message
     */
    public function setMessage($message)
    {
        $this->_message = $message;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/SpicaErrorListener#setReporter()
     */
    public function setReporter($reporter)
    {
        $this->_reporter = $reporter;
    }

    /**
     * Gets set repoter.
     *
     * @return SpicaErrorReporter
     */
    public function getReporter()
    {
        if (null === $this->_reporter)
        {
            $this->_reporter = new SpicaDefaultErrorReporter();
        }

        return $this->_reporter;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/SpicaErrorListener#onError()
     */
    public function onError($level, $error, $file, $line)
    {
        include_once 'library/spica/core/Debug.php';

        if (false == ($level & error_reporting()))
        {
            return;
        }

        if (null === $this->_reporter)
        {
            $this->_reporter = new SpicaDefaultErrorReporter();
        }

        $levels = array(
            8191 => 'E_ALL',
            4096 => 'E_RECOVERABLE_ERROR',
            2048 => 'E_STRICT',
            2047 => 'E_ALL',
            1024 => 'E_USER_NOTICE',
            512  => 'E_USER_WARNING',
            256  => 'E_USER_ERROR',
            128  => 'E_COMPILE_WARNING',
            64   => 'E_COMPILE_ERROR',
            32   => 'E_CORE_ERROR',
            8    => 'E_NOTICE',
            4    => 'E_PARSE',
            2    => 'E_WARNING',
            1    => 'E_ERROR',
        );

        $report              = new SpicaErrorReport();
        $report->level       = $level;
        $report->levelName   = $levels[$level];
        $report->file        = $file;
        $report->line        = $line;
        $report->message     = $error;
        $report->userMessage = $this->_message;
        $report->trace       = SpicaDebug::getTrace(self::BACKTRACE_TEXT);
        $this->_reporter->report($report);

        if (ini_get('display_errors'))
        {
            echo $error;
        }

        // Don't execute PHP internal error handler
        return true;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/SpicaErrorListener#onShutdown()
     */
    public function onShutdown()
    {
        // Fatal errors cause normal exit
        if (null === ($errorInfo = error_get_last()) && CONNECTION_NORMAL === connection_status())
        {
            // Normal exit without errors (add a hook here?)
            return true;
        }

        if (CONNECTION_ABORTED === connection_status())
        {
            return $this->onConnectionAborted();
        }

        if (CONNECTION_TIMEOUT === connection_status())
        {
            return $this->onConnectionTimeout();
        }

        return $this->_handleFatalError(3, $errorInfo);
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/SpicaErrorListener#onConnectionAborted()
     */
    public function onConnectionAborted()
    {
        Spica::logError('Connection Aborted', 0);
        return true;
    }

    /**
     * (non-PHPdoc)
     * @see library/spica/core/SpicaErrorListener#onConnectionTimeout()
     */
    public function onConnectionTimeout()
    {
        $errorInfo      = error_get_last();
        $this->_message = 'The execution time limit has been reached. The server seems to be too busy at the moment.';
        return $this->_handleFatalError(2, $errorInfo);
    }

    /**
     * Handles fatal errors.
     *
     * @param  int   $event The event code: 2- Timeout 3- Fatal error
     * @param  array $errorInfo
     * @return bool Always returns true
     */
    protected function _handleFatalError($event, $errorInfo = array())
    {
        include_once 'library/spica/core/Debug.php';

        $userMessage = (null === $this->_message) ? $this->_message : 'A serious systematic error has been occured. ';
        $line        = $errorInfo['line'];
        $file        = $errorInfo['file'];
        $level       = $errorInfo['type'];
        $message     = $errorInfo['message'];

        if (null === $this->_reporter)
        {
            $this->_reporter = new SpicaDefaultErrorReporter();
        }

        $levels = array(
            8191 => 'E_ALL',
            4096 => 'E_RECOVERABLE_ERROR',
            2048 => 'E_STRICT',
            2047 => 'E_ALL',
            1024 => 'E_USER_NOTICE',
            512  => 'E_USER_WARNING',
            256  => 'E_USER_ERROR',
            128  => 'E_COMPILE_WARNING',
            64   => 'E_COMPILE_ERROR',
            32   => 'E_CORE_ERROR',
            8    => 'E_NOTICE',
            4    => 'E_PARSE',
            2    => 'E_WARNING',
            1    => 'E_ERROR',
        );

        $report              = new SpicaErrorReport();
        $report->level       = $level;
        $report->levelName   = $levels[$level];
        $report->file        = $file;
        $report->line        = $line;
        $report->message     = $message;
        $report->userMessage = $userMessage;
        $report->trace       = SpicaDebug::getTrace(self::BACKTRACE_TEXT);
        $this->_reporter->report($report);

        while (ob_get_level())
        {
            ob_end_clean();
        }

        // Command line only
        if ('cli' === PHP_SAPI)
        {
            echo 'Seriousity: ', $level, "\n";
            echo 'Message: ', $message, "\n";
            echo 'Source: ', $file, ':', $line, "\n";
            return true;
        }

        // Web environment only
        SpicaResponse::setHeader('Error-Source', 'App');

        $sapiName = php_sapi_name();

        if ('cgi' === $sapiName || 'cgi-fcgi' === $sapiName)
        {
            SpicaResponse::setHeader('Status',  '500 Internal Server Error');
        }
        else
        {
            if (true === in_array($level, array(1, 4, 32, 64, 256)))
            {
                SpicaResponse::setStatusCode(500);
                SpicaResponse::setStatusText('Internal Server Error');
            }

            if (true === SpicaRequest::isAjax() || true === SpicaRequest::isFlash())
            {
                // Return Ajaxified response asynchronously
                SpicaResponse::setHeader('Content-Type', 'application/json');
                $message      = 'An internal server error occurs with messsage "'.$message.'" at '.spica_path_make_relative($file).':'.$line.' ('.$levels[$level].')';
                $responseBody = new SpicaAjaxResponseBody('10', (array) $message, '', null);
                SpicaResponse::setBody($responseBody->toJson());
            }
            else
            {
                SpicaResponse::setBody($this->_fetchErrorPage($level, $userMessage, $message, $file, $line));
            }
        }

        SpicaResponse::send();
        return true;
    }

    /**
     * Fetches the content of the error page that is designed for reporting fatal or internal application errors.
     *
     * @param  int    $level       Error level
     * @param  string $userMessage Error message for user
     * @param  string $message     Error message for debugging
     * @param  string $file        The file where error occurs
     * @param  int    $line        The line where error occurs
     * @return string
     */
    protected function _fetchErrorPage($level, $userMessage, $message, $file, $line)
    {
        ob_start();
        include $this->_options['template_path'];
        return ob_get_clean();
    }
}

/**
 * A SpicaLogger object is used to log messages for a specific system or application component
 *
 * namespace spica\core\action
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      May 20, 2010
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaLogger
{
    /**
     * File to store ERRO message.
     *
     * @var string
     */
    public static $errorFile = './error.log';

    /**
     * File to store INFO message.
     *
     * @var string
     */
    public static $infoFile = './info.log';

    /**
     * Logs a message to a file.
     *
     * @param string $file File path
     * @param string $message Message
     */
    public static function log($file, $message)
    {
        try
        {
            $handle = fopen($file, "ab");
            fwrite($handle, date("Y-m-d\Tg:i:s") . ' ' . $message . "\n");
            fclose($handle);
        }
        catch (Exception $e)
        {
            // Silently ignore logging errors
        }
    }

    /**
     * Logs a informative message into a file.
     *
     * @param string $message
     */
    public static function info($message)
    {
        self::log(self::$infoFile, 'INFO '.$message);
    }

    /**
     * Logs a warning message into a file.
     *
     * @param string $message
     */
    public static function warn($message)
    {
        self::log(self::$infoFile, 'WARN '.$message);
    }

    /**
     * Logs a debugging message into a file.
     *
     * @param string $message
     */
    public static function debug($message)
    {
        self::log(self::$infoFile, 'DEBG '.$message);
    }
}

/**
 * The ActionRequest represents the request sent to the Controller to handle
 * an action. The Spica context normalizes HTTP request into $_GET array and use
 * it as request-related data keeper to the Controller's action methods.
 *
 * namespace spica\core\action
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaRequest
{
    /**
     * Returns the value of the named attribute for this <code>GET</code> request,
     * or <code>null</code> if no attribute of the given name exists.
     *
     * @param  string $name specifying the name of the attribute
     * @return array|string|null
     */
    public static function get($name)
    {
        return (true === isset($_GET[$name])) ? $_GET[$name] : null;
    }

    /**
     * Stores an attribute in this <code>GET</code> request.
     *
     * @param string $name  A string specifying the name of the attribute
     * @param string $value A value to store
     */
    public static function set($name, $value)
    {
        $_GET[$name] = $value;
    }

    /**
     * Returns the value of the attribute 'page' for this <code>GET</code> request,
     * or <code>'1'</code> if no attribute of the given name exists or its interger value is not valid.
     *
     * @param  string $name specifying the name of the page attribute, default to 'page'
     * @return string A interger string
     */
    public static function getPage($name = 'page')
    {
        if (true === isset($_GET[$name]))
        {
            if (true === is_numeric($_GET[$name]) && $_GET[$name] == (int) $_GET[$name] && $_GET[$name] > 0 && $_GET[$name] < PHP_INT_MAX)
            {
                return $_GET[$name];
            }
        }

        return '1';
    }

    /**
     * Gets request method.
     *
     * @param  string $default Default request method
     * @return string
     */
    public static function getMethod($default = 'GET')
    {
        return (true === isset($_SERVER['REQUEST_METHOD'])) ? $_SERVER['REQUEST_METHOD'] : $default;
    }

    /**
     * Gets request module name.
     *
     * @return string|null
     */
    public static function getModule()
    {
        return (true === isset($_GET['_module'])) ? $_GET['_module'] : null;
    }

    /**
     * Sets request module to a particular name.
     *
     * @param string $value
     */
    public static function setModule($value)
    {
        $_GET['_module'] = (string) $value;
    }

    /**
     * Returns the controller name from the current request, or null if the request
     * does not specify controller name.
     *
     * @return string|null
     */
    public static function getController()
    {
        return (true === isset($_GET['_controller'])) ? $_GET['_controller'] : null;
    }

    /**
     * Stores the given controller name in the current request, using a request attribute.
     *
     * @param string $value
     */
    public static function setController($value)
    {
        $_GET['_controller'] = (string) $value;
    }

    /**
     * Returns the action name from the current request, or null if the request
     * does not specify action name.
     *
     * @return string|null
     */
    public static function getAction()
    {
        return (true === isset($_GET['_action'])) ? $_GET['_action'] : null;
    }

    /**
     * Stores the given action name in the current request, using a request attribute.
     *
     * @param string $value
     */
    public static function setAction($value)
    {
        $_GET['_action'] = (string) $value;
    }

    /**
     * Gets a named parameter for this <code>POST or GET</code> request.
     *
     * @param  string $name
     * @return string|null|array
     */
    public static function getParam($name)
    {
        return (true === isset($_REQUEST[$name])) ? $_REQUEST[$name] : null;
    }

    /**
     * Sets a parameter for this <code>POST or GET</code> request,
     * using a request attribute.
     *
     * @param string       $name
     * @param string|array $value
     */
    public static function setParam($name, $value)
    {
        $_REQUEST[$name] = $value;
    }

    /**
     * Gets a $_POST parameter value.
     *
     * @return string|null|array
     */
    public static function getPost($name)
    {
        return (true === isset($_POST[$name])) ? $_POST[$name] : null;
    }

    /**
     * Sets a value to $_POST parameter.
     *
     * @param string       $name
     * @param string|array $value
     */
    public static function setPost($name, $value)
    {
        $_POST[$name] = $value;
    }

    /**
     * Checks if request method is a POST.
     *
     * @return bool
     */
    public static function isPost()
    {
        return ('POST' === self::getMethod());
    }

    /**
     * Checks if request method is a GET.
     *
     * @return bool
     */
    public static function isGet()
    {
        return ('GET' === self::getMethod());
    }

    /**
     * Checks if request method is a PUT.
     *
     * @return bool
     */
    public static function isPut()
    {
        return ('PUT' === self::getMethod());
    }

    /**
     * Was the request made by DELETE?
     *
     * @return bool
     */
    public static function isDelete()
    {
        return ('DELETE' === self::getMethod());
    }

    /**
     * Was the request made by HEAD?
     *
     * @return bool
     */
    public static function isHead()
    {
        return ('HEAD' === self::getMethod());
    }

    /**
     * Was the request made by OPTIONS?
     *
     * @return bool
     */
    public static function isOptions()
    {
        return ('OPTIONS' === self::getMethod());
    }

    /**
     * Checks if request is sent with file upload via HTTP POST
     *
     * @param  string $inputName
     * @return bool
     */
    public static function isFilePost($inputName = null)
    {
        if (null !== $inputName)
        {
            return is_uploaded_file($_FILES[$inputName]['tmp_name']);
        }

        return (bool) count($_FILES);
    }

    /**
     * Returns the raw entity body of the request, if present
     *
     * @return string|false Raw entity body, or false if not present
     */
    public static function getRawBody()
    {
        $body = file_get_contents('php://input');
        return (strlen(trim($body)) > 0) ? $body : false;
    }

    /**
     * Returns the value of the given HTTP header
     * Pass the header name as the plain, HTTP-specified header name
     *
     * Ex.: Ask for 'Accept' to get the Accept header, 'Accept-Encoding'
     * to get the Accept-Encoding header.
     *
     * @throws SpicaException
     * @param  string HTTP header name
     * @return string|false HTTP header value, or false if not found
     */
    public static function getHeader($header)
    {
        if (true === empty($header))
        {
            throw new SpicaException('An HTTP header name is required');
        }

        // Try to get it from the $_SERVER array first
        $temp  = 'HTTP_' . strtoupper(str_replace('-', '_', $header));

        if (false === empty($_SERVER[$temp]))
        {
            return $_SERVER[$temp];
        }

        // This seems to be the only way to get the Authorization header on Apache
        if (true === function_exists('apache_request_headers'))
        {
            $headers  = apache_request_headers();

            if (false === empty($headers[$header]))
            {
                return $headers[$header];
            }
        }

        return false;
    }

    /**
     * Gets user agent. The same as SpicaRequest::getHeader("User-Agent")
     *
     * @return string|null
     */
    public static function getUserAgent()
    {
        return (true === isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT'] : null;
    }

    /**
     * Checks if user is using Internet Explorer
     *
     * @return bool
     */
    public static function isIE()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE');
    }

    /**
     * Checks if user is using Internet Explorer 6.0.x
     *
     * @return bool
     */
    public static function isIE6()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE 6.0');
    }

    /**
     * Checks if user is using Internet Explorer 7.0.x
     *
     * @return bool
     */
    public static function isIE7()
    {
        return (false !== strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE 7.0')) && (false === strstr($_SERVER['HTTP_USER_AGENT'], 'Trident/4.0'));
    }

    /**
     * Checks if user is using Internet Explorer 8.0.x.
     *
     * IE8 on Windows Vista (Compatibility View)
     *   Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)
     * IE8 on Windows Vista
     *   Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)
     *
     * @return bool
     */
    public static function isIE8()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Trident/4.0');
    }

    /**
     * Checks if user is using Firefox
     *
     * @return bool
     */
    public static function isFirefox()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Firefox');
    }

    /**
     * Checks if user is using Firefox 3.5
     *
     * @return bool
     */
    public static function isFirefox35()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Firefox/3.5');
    }

    /**
     * Checks if user is using Firefox 3.6
     *
     * @return bool
     */
    public static function isFirefox36()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Firefox/3.6');
    }

    /**
     * Checks if user is using Safari
     *
     * @return bool
     */
    public static function isSafari()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Safari');
    }

    /**
     * Checks if user is using Opera
     *
     * @return bool
     */
    public static function isOpera()
    {
        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Opera');
    }

    /**
     * Checks if user is using Opera 9
     *
     * @param  string $minnor Minnor version (51: 9.51)
     * @return bool
     */
    public static function isOpera9($minnor = null)
    {
        if (null === $minnor)
        {
            return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Opera/9');
        }

        return false !== strstr($_SERVER['HTTP_USER_AGENT'], 'Opera/9.'.$minnor);
    }

    /**
     * Gets HTTP Referer
     *
     * @return string Empty string if referer is not set and full string if referer is set
     */
    public static function getReferer()
    {
        $referer = getenv('HTTP_REFERER');
        return (false === $referer) ? '': $referer;
    }

    /**
     * Is the request a Javascript XMLHttpRequest?
     *
     * Should work with Prototype, Script.aculo.us, JQuery, possibly others.
     *
     * @return bool
     */
    public static function isAjax()
    {
        return ('XMLHttpRequest' === self::getHeader('X_REQUESTED_WITH'));
    }

    /**
     * Is this a Flash request?
     *
     * @return bool
     */
    public static function isFlash()
    {
        return ('Shockwave Flash' === self::getHeader('USER_AGENT'));
    }

    /**
     * Returns true if user agent string matches a mobile web browser
     *
     * @return bool True if user agent is a mobile web browser
     */
    public static function isMobile()
    {
        $mobile = '(AvantGo|BlackBerry|DoCoMo|NetFront|Nokia|PalmOS|PalmSource|portalmmm|Plucker|ReqwirelessWeb|SonyEricsson|Symbian|UP\.Browser|Windows CE|Xiino)';
        return (preg_match('/'.$mobile.'/i', env('HTTP_USER_AGENT')) > 0);
    }

    /**
     * Tests if the request is made through a SSL connection.
     * The HTTPS element is set by Apache modssl.
     *
     * @see    http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25
     * @return bool True if request is made via a secure connection.
     */
    public static function isSecure()
    {
        // $_SERVER entries are set by the web server.
        return (true === isset($_SERVER['HTTPS']) && 'on' === $_SERVER['HTTPS']) ? true : false;
    }

    /**
     * Gets HTTP version that the request uses.
     *
     * @return bool True if request is made via a secure connection.
     */
    public static function getHttpVersion()
    {
        return (false !== strpos($_SERVER['SERVER_PROTOCOL'], '1.0')) ? '1.0' : '1.1';
    }

    /**
     * Gets real IP address (even they are behind proxy - but not all)
     *
     * @return string
     */
    public static function getRealIp()
    {
        if (true === isset($_SERVER))
        {
            if (true === isset($_SERVER['HTTP_X_FORWARDED_FOR']))
            {
                return $_SERVER['HTTP_X_FORWARDED_FOR'];
            }

            if (true === isset($_SERVER['HTTP_CLIENT_IP']))
            {
                return $_SERVER['HTTP_CLIENT_IP'];
            }

            // CLI check
            return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
        }
        elseif (getenv('HTTP_X_FORWARDED_FOR'))
        {
            return getenv('HTTP_X_FORWARDED_FOR');
        }
        elseif (getenv('HTTP_CLIENT_IP'))
        {
            return getenv('HTTP_CLIENT_IP');
        }

        return getenv('REMOTE_ADDR');
    }

    /**
     * Returns the Unix timestamp of the If-Modified-Since HTTP header or the
     * current time if the header was not sent by the client.
     *
     * @return string
     */
    public static function getCacheStart()
    {
        if (true === isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && false === self::isPost())
        {
            return strtotime(current($array = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])));
        }

        return time();
    }

    /**
     * Tests if the request content cache is expired from client side.
     *
     * @param  int $expires The time in seconds that content will be expired after being sent to client
     * @return bool
     */
    public static function isCacheExpired($expires = 0)
    {
        // Discourage to cache POST data
        if (true === self::isPost())
        {
            return true;
        }

        if (true === isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
        {
            return (strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= time() - $expires);
        }

        return true;
    }

    /**
     * Gets full application URL (module/controller/action/parameters)
     *
     * This method will return a string which looks like: <code>home/index/index</code>
     * or <code>account/update/profile/id/3434/role/5</code>
     *
     * @param  bool $new True if you decide to not get the cache value
     * @return string
     */
    public static function getBaseAppUrl($new = false)
    {
        if (false === $new && !empty($GLOBALS['__state__']['base_app_url']))
        {
            return $GLOBALS['__state__']['base_app_url'];
        }

        foreach ($_GET as $name => $value)
        {
            $url[] = $name.'/'.$value;
        }

        $GLOBALS['__state__']['base_app_url'] = implode('/', $url);
        return $GLOBALS['__state__']['base_app_url'];
    }

    /**
     * Gets the full base URL including protocol, host name, subdir.
     * Traling slash is returned by default.
     *
     * @param  bool $trailingSlash Defaulted to true
     * @return string
     */
    public static function getFullBaseUrl($trailingSlash = true)
    {
        if (true   === empty($GLOBALS['__state__']['full_base_url']))
        {
            $s     = (true  === isset($_SERVER['HTTPS']) && 'on' === $_SERVER['HTTPS'])?'s':'';
            $host  = getenv('HTTP_HOST');

            if ('' === $host || null === $host)
            {
                $host = $_SERVER['SERVER_NAME'];
            }

            $GLOBALS['__state__']['full_base_url'] = 'http'.$s.'://'.$host.substr($_SERVER['SCRIPT_NAME'], 0, -9);
        }

        if (true === $trailingSlash)
        {
            return $GLOBALS['__state__']['full_base_url'];
        }

        // Remove traling slash
        return rtrim($GLOBALS['__state__']['full_base_url'], '/');
    }
}

/**
 * This class provides HTTP-specific functionality in sending a response.
 * For example, it has methods to access HTTP headers and cookies.
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaResponse
{
    /**
     * Gets response body.
     *
     * @return string|null
     */
    public static function getBody()
    {
        if (304 === SpicaResponse::getStatusCode())
        {
            // Do not send content body
            return null;
        }

        return (true === isset($GLOBALS['__response__']['body'])) ? $GLOBALS['__response__']['body'] : null;
    }

    /**
     * Associates a response body with a value.
     *
     * @param string $value Response body
     */
    public static function setBody($value = null)
    {
        $GLOBALS['__response__']['body'] = $value;
    }

    /**
     * Associates a response header with a value.
     *
     * @param string $header HTTP header name
     * @param string $value  HTTP header value
     */
    public static function setHeader($header, $value)
    {
        $GLOBALS['__response__']['headers'][$header] = $value;
    }

    /**
     * Sets neccessary hidden headers to hand it off to nginx to send the file
     * once the authentication has succeeded.
     *
     * You will need to add a location directive in nginx marked as internal which
     * nginx will use along with your path to get to the physical file.
     *
     * location /downloads {
     *   root /rails_deploy/current/downloads;
     *   # Marked internal so that this location cannot be accessed directly.
     *   internal;
     * }
     *
     * Please note that sometimes you can get this error from nginx
     * "upstream sent too big header while reading response header from upstream".
     * This means that and your upstream is a lamp server (possibly for others too),
     * the problem might be that you are sending a header that is too big for the
     * current settings of nginx. The solution is to increase the buffers sizes in nginx
     *
     * proxy_connect_timeout 90;
     * proxy_send_timeout 180;
     * proxy_read_timeout 180;
     * proxy_buffer_size 16k;
     * proxy_buffers 8 16k;
     * proxy_busy_buffers_size 32k;
     * proxy_intercept_errors on;
     *
     * @see   http://wiki.codemongers.com/NginxXSendfile
     * @param string $relativeFilePath the path relative to the /downloads location in nginx
     * @param string $httpFileName     file name that is displayed to user when the download completes.
     */
    public static function sendFileUsingNginx($relativeFilePath, $httpFileName = 'file.zip')
    {
        $GLOBALS['__response__']['headers']['X-Accel-Redirect']    = $relativeFilePath;
        $GLOBALS['__response__']['headers']['Content-Type']        = 'application/octet-stream';
        $GLOBALS['__response__']['headers']['Content-Disposition'] = 'attachment; filename='.$httpFileName;
    }

    /**
     * Sets the HTTP response status text.
     *
     * @param string $text The status text; if empty, will set the text to the
     * default for the current status code.
     */
    public static function setStatusText($text)
    {
        // trim and remove newlines from custom text
        $text = trim(str_replace(array("\r", "\n"), '', $text));

        if (false === empty($text))
        {
            // use custom text
            $GLOBALS['__response__']['status_text'] = $text;
        }
    }

    /**
     * Gets the HTTP response status text.
     *
     * @return string The status text;
     */
    public static function getStatusText()
    {
        return (true === isset($GLOBALS['__response__']['status_text'])) ? $GLOBALS['__response__']['status_text'] : null;
    }

    /**
     * Sets a cookie value. Those values will be sent to the client.
     *
     * @param string $name The name of the cookie.
     * @param string $value The value of the cookie.
     * @param int|string $expire The Unix timestamp after which the cookie
     *        expires. If non-numeric, the method uses strtotime() on the value.
     * @param string $path The path on the server in which the cookie will be
     *        available on.
     * @param string $domain The domain that the cookie is available on.
     * @param bool $secure Indicates that the cookie should only be
     *        transmitted over a secure HTTPS connection.
     * @param bool $httpOnly When true, the cookie will be made accessible
     *        only through the HTTP protocol. This means that the cookie won't be
     *        accessible by scripting languages, such as JavaScript.
     */
    public static function setCookie($name, $value = '', $expire = 0, $path = '', $domain = '', $secure = false, $httpOnly = null)
    {
        $GLOBALS['__response__']['cookies'][$name] = array(
            'value'    => $value,
            'expire'   => $expire,
            'path'     => $path,
            'domain'   => $domain,
            'secure'   => $secure,
            'httponly' => $httpOnly,
        );
    }

    /**
     * Sends HTTP headers to user agent
     */
    public static function sendHeader()
    {
        if (true === headers_sent())
        {
            return;
        }

        // build the full status header string.
        $version    = SpicaResponse::getVersion();
        $statusCode = SpicaResponse::getStatusCode();
        $statusText = SpicaResponse::getStatusText();
        $status     = 'HTTP/'.$version.' '.$statusCode;

        if (false === empty($statusText))
        {
            $status .= ' '.$statusText;
        }

        // send the status header
        header($status, true, $statusCode);
        // Safari cross domain cookie
        // header('P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"');

        // send each of the remaining headers
        foreach ($GLOBALS['__response__']['headers'] as $headerName => $values)
        {
            // send each value for the header
            foreach ((array) $values as $val)
            {
                // we don't need full MIME escaping here, just sanitize the
                // value by stripping CR and LF chars
                $val = str_replace(array("\r", "\n"), '', $val);
                header($headerName.': '.$val);
            }
        }

        // send each of the cookies
        foreach ($GLOBALS['__response__']['cookies'] as $key => $val)
        {
            // was httponly set for this cookie?  if not,
            // use the default.
            $httpOnly = ($val['httponly'] === null)?true:(bool) $val['httponly'];

            // try to allow for times not in unix-timestamp format
            if (false === is_numeric($val['expire']))
            {
                $val['expire'] = strtotime($val['expire']);
            }

            // actually set the cookie
            setcookie($key, $val['value'], (int) $val['expire'], $val['path'], $val['domain'], (bool) $val['secure'], (bool) $httpOnly);
        }
    }

    /**
     * Sets response HTTP headers to request client to cache the response content at client side.
     *
     * @param int $expire The seconds that the content will be expired after being sent to the client.
     * @param int $lastModified Unix timestamp
     */
    public static function enableCache($expire, $lastModified = null)
    {
        if (null === $lastModified)
        {
            $lastModified = time();
        }

        SpicaResponse::setHeader('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
        SpicaResponse::setHeader('Expires', gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT');
        SpicaResponse::setHeader('Cache-Control', 'max-age='. $expire);
        SpicaResponse::setHeader('Pragma', 'cache');
    }

    /**
     * Disables browser caching.
     */
    public static function disableCache()
    {
        // Date in the past
        SpicaResponse::setHeader('Expires', 'Sat, 14 May 1980 00:00:00 GMT');
        SpicaResponse::setHeader('Last-Modified'. gmdate("D, d M Y H:i:s") . ' GMT');
        // To make sure that browser sends conditional request.
        // Cache-Control: must-revalidate means that browser must send validation request
        // every time even if there is already cache entry exists for this object
        // no-cache - force both proxy and browser to validate the document before to provide a cached copy;
        SpicaResponse::setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
        SpicaResponse::setHeader('Pragma', 'no-cache');
        SpicaResponse::setHeader('Expires', '-1');
    }

    /**
     * Sets the HTTP version to '1.0' or '1.1'.
     *
     * @param  string $version The HTTP version to use for this response.
     * @return bool
     */
    public static function setVersion($version)
    {
        $version = trim($version);

        if ('1.0' != $version && '1.1' != $version)
        {
            return false;
        }

        $GLOBALS['__response__']['response_version'] = $version;
        return true;
    }

    /**
     * Gets the HTTP version to '1.0' or '1.1'.
     *
     * @return bool
     */
    public static function getVersion()
    {
        return $GLOBALS['__response__']['response_version'];
    }

    /**
     * Sets the HTTP response status code.
     * Automatically resets the status text to the default for this code.
     *
     * @param  int $code An HTTP status code, such as 200, 302, 404, etc.
     * @return bool
     */
    public static function setStatusCode($code)
    {
        $code = (int) $code;

        if ($code < 100 || $code > 599)
        {
            return false;
        }

        $GLOBALS['__response__']['status_code'] = $code;
        return true;
    }

    /**
     * Gets the HTTP response status code.
     *
     * @return string
     */
    public static function getStatusCode()
    {
        return $GLOBALS['__response__']['status_code'];
    }

    /**
     * Sends response header and payload to user agent.
     */
    public static function send()
    {
        Spica::publishEvent('spica_response_start');
        SpicaResponse::sendHeader();
        echo SpicaResponse::getBody();
        Spica::publishEvent('spica_response_stop');
    }

    /**
     * Sends HTTP header and payload in response to a conditional request.
     *
     * @param int $timestamp The Unix timestamp in which the content is modified.
     */
    public static function sendConditionally($timestamp)
    {
        if (true === SpicaRequest::isPost())
        {
            return SpicaResponse::send();
        }

        $lastModified = gmdate("D, d M Y H:i:s \G\M\T", $timestamp);
        $etag         = '"'.md5($lastModified).'"';
        SpicaResponse::setHeader('Last-Modified', $lastModified);
        SpicaResponse::setHeader('ETag', $etag);

        $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
        $ifNoneMatch     = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false;

        // none exists
        if (false === $ifModifiedSince && false === $ifNoneMatch)
        {
            return SpicaResponse::send();
        }

        // If $ifNoneMatch exists, validate it
        if (false !== $ifNoneMatch && $ifNoneMatch !== $etag)
        {
            return SpicaResponse::send();
        }

        // If $ifModifiedSince exists, validate it
        if ($ifModifiedSince && $ifModifiedSince != $lastModified)
        {
            return SpicaResponse::send();
        }

        return SpicaResponse::sendNoChange();
    }

    /**
     * Sends a Basic Authentication Scheme response to user agent.
     *
     * @see   http://www.faqs.org/rfcs/rfc2617.html
     * @param string $notice  The message that is shown to user when log-in dialog appears.
     * @param string $message The message that is shown to user when user tries to cancel the dialog.
     */
    public static function sendBasicDigest($notice = null, $message = 'Please enter a valid user name and password.')
    {
        if (null === $notice)
        {
            $notice = 'You are required to log in your account before this operation';
        }

        Spica::publishEvent('spica_response_start');
        SpicaResponse::setHeader('WWW-Authenticate', 'Basic realm="'.$notice.'"');
        SpicaResponse::setHeader('HTTP/1.0 401', 'Unauthorized');
        SpicaResponse::setBody($message);
        Spica::publishEvent('spica_response_stop');
    }

    /**
     * Sends a XPI file to browser. This will display the familiar 'save/open' dialog
     * if the xpi extension is not supported. Firefox recognize this file extension.
     *
     * @param string $filePath
     */
    public static function sendXpi($filePath)
    {
        SpicaResponse::setHeader('Content-Disposition', 'filename='.basename($filePath));
        // Tell the browser that the content that is coming is an xpinstall
        SpicaResponse::setHeader('Content-type', 'application/x-xpinstall');
        // Also send the content length
        SpicaResponse::setHeader('Content-Length', filesize($filePath));
        // readfile reads the file content and echos it to the output
        readfile($filePath);
    }

    /**
     * Sends a response of no body to the request client together with 304 HTTP header
     */
    public static function sendNoChange()
    {
        SpicaResponse::setStatusText('Not Modified');
        SpicaResponse::setStatusCode(304);
        SpicaResponse::send();
    }

    /**
     * Issues an immediate "Location" redirect.
     * Use instead of display() to perform a redirect.
     * You should die() or exit() after calling this.
     *
     * @param array|string $spec The URI to redirect to.
     * @param int|string   $code The HTTP status code to redirect with; default is '302 Found'.
     */
    public static function redirect($spec, $code = '302')
    {
        if (true === is_array($spec))
        {
            $href = SpicaContext::buildUrl($spec);
        }
        elseif (strpos($spec, '://') !== false)
        {
            // external link, protect against header injections
            $href = str_replace(array("\r", "\n"), '', $spec);
        }
        else
        {
            $href = 'http://'.$spec;
        }

        // kill off all output buffers
        while(@ob_end_clean());
        // make sure there's actually an href
        $href = trim($href);
        // set the status code
        SpicaResponse::setStatusCode($code);
        // set the redirect location
        SpicaResponse::setHeader('Location', $href);
        // clear the response body
        SpicaResponse::setBody(null);
        // is this a GET-after-(POST|PUT) redirect?
        if (true === SpicaRequest::isPost() || true === SpicaRequest::isPut())
        {
            // tell the next request object that it's a get-after-post
            SpicaSession::setFlash('is_gap', true);
        }

        // save the session
        session_write_close();
        // send the response directly
        SpicaResponse::send();
    }

    /**
     * Redirects to another page and action after disabling HTTP caching.
     * This effectively implements the "POST-Redirect-GET" pattern (also known
     * as the "GET after POST").
     *
     * The redirect() method is often called after a successful POST
     * operation, to show a "success" or "edit" page. In such cases, clicking
     * clicking "back" or "reload" will generate a warning in the
     * browser allowing for a possible re-POST if the user clicks OK.
     * Typically this is not what you want.
     *
     * In those cases, use redirectNoCache() to turn off HTTP caching, so
     * that the re-POST warning does not occur.
     *
     * @param array|string $spec The URI to redirect to. If $spec is an array, it contains
     *        the following keys: module, controller, action, param keys and special key: # to indicate base URL
     * @param int|string   $code The HTTP status code to redirect with; default is '303 See Other'.
     */
    public static function sendRedirectNocache($spec, $code = '303')
    {
        // reset pragma header
        SpicaResponse::setHeader('Pragma', 'no-cache');
        // reset cache-control
        SpicaResponse::setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
        // append cache-control
        SpicaResponse::setHeader('Cache-Control', 'post-check=0, pre-check=0');
        // force immediate expiration
        SpicaResponse::setHeader('Expires', 'Thu, 14 May 1980 00:00:00 GMT');
        // continue with redirection
        return SpicaResponse::redirect($spec, $code);
    }

    /**
     * Renders a page that notifies user that the requested page cannot be found.
     *
     * @param string $message
     */
    public static function sendNotFound($message = null)
    {
        Spica::publishEvent('spica_page_not_found');
        SpicaResponse::setHeader('Status', '404 Page not found');
        ob_start();
        include SpicaContext::getBasePath().'/library/spica/core/templates/error/notfound.tpl.php';
        SpicaResponse::setBody(ob_get_clean());
    }
}

/**
 * This class represents a "Cookie", as used for session or state management
 * with HTTP and HTTPS protocols. Cookies are used to get user agents
 * (web browsers etc) to hold small amounts of state associated with a user's
 * web browsing. Common applications for cookies include storing user preferences,
 * automating low security user signon facilities, and helping collect data used
 * for "shopping cart" style applications.
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaCookie
{
    /**
     * Gets the HTTP cookie value by a name.
     *
     * @param  string $name
     * @return string|array|null Cookie name like cookie[three] which is set by setcookie can be an array
     */
    public static function get($name)
    {
        return (true === isset($_COOKIE[$name])) ? $_COOKIE[$name] : null;
    }

    /**
     * Expires browser cookies.
     *
     * @param  string $name   cookie name
     * @param  string $path   URL path
     * @param  string $domain URL domain
     * @return bool
     */
    public static function expire($name, $path = null, $domain = null)
    {
        // Sets the cookie value to an empty string, and the expiration to 24 hours ago
        return SpicaResponse::setCookie($name, '', -86400, $path, $domain, false, false);
    }
}

/**
 * Provides some static methods for controller operations.
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009 (rewritten on December 31, 2009)
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaController
{
    /**
     * Processing result object.
     *
     * @var object
     */
    private static $_processResult;

    /**
     * Gets output of a section controller.
     *
     * Section controller is view-centric, making page sections reusable between pages.
     * Section controllers can be instantiated and called by a main controller.
     * It is designed to use in a main controller only. Don't abused it.
     *
     * @throws InvalidArgumentException if class name is not valid
     * @param  string $className
     * @return string
     */
    public static function invoke($className)
    {
        // Secure file name
        if (false === ctype_alnum(str_replace(array('/', '_'), '', $className)))
        {
            throw new InvalidArgumentException('Invalid section name: '.$className);
        }

        $basePath = SpicaContext::getThemeBasePath().'/'.SpicaContext::getTheme();

        // check class name suffix: Block|5 and BlockGroup|10
        // ends with Block
        if (substr($className, -5) === 'Block')
        {
            $file = spica_block_classpath().'/'.$className.'.php';
            include_once $file;

            // We don't check file existence before but this check is for all.
            // See comment in SpicaPageBlockGroup#getBlock()
            if (false === class_exists($file))
            {
                throw new InvalidArgumentException('Class named '.$className.' can not be found in block directory. ');
            }

            return new $className('.tpl.php', $basePath);
        }

        // ends with BlockGroup
        if (substr($className, -10) === 'BlockGroup')
        {
            $file = spica_blockgroup_classpath().'/'.$className.'.php';
            include_once $file;

            // We don't check file existence before but this check is for all.
            // See comment in SpicaPageLayout#getBlockGroup()
            if (false === class_exists($file))
            {
                throw new InvalidArgumentException('Class named '.$className.' can not be found in blockgroup directory. ');
            }

            return new $className('.tpl.php', $basePath);
        }

        throw new InvalidArgumentException('The class name that is passed to the method '.__FUNCTION__.' must end with Block or BlockGroup. ');
    }

    /**
     * Forwards the request handling task to another controller/action.
     *
     * @param  string $controller Target controller name
     * @param  string $action     Target action name
     * @param  string $module     Target module name
     * @param  array  $params     New request parameters
     * @return string
     */
    public static function forward($controller, $action, $module = null, $params = array())
    {
        if (null === $controller)
        {
            throw new InvalidArgumentException('Controller name can not be null.');
        }

        if (null === $action)
        {
            throw new InvalidArgumentException('Action name can not be null.');
        }

        $_GET = array_merge($_GET, $params);
        $p['_module']     = (null === $module) ? SpicaRequest::getModule() : $module;
        $p['_controller'] = $controller;
        $p['_action']     = $action;
        return SpicaContext::getDispatcher()->dispatch($p);
    }

    /**
     * Sets processing result in controller
     *
     * @param object $rs
     */
    public static function setProcessResult($rs)
    {
        self::$_processResult = $rs;
    }

    /**
     * Gets processing result in controller
     *
     * @return object
     */
    public static function getProcessResult()
    {
        return self::$_processResult;
    }
}

/**
 * Provides static methods to access Spica configuration.
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009 (rewritten on December 31, 2009)
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaConfig
{
    /**
     * Gets database-related configuration.
     *
     * @throws InvalidArgumentException if alias name can not be found.
     * @param  string $alias Defaults to 'db1'
     * @return array|null
     */
    public static function getDb($alias = 'db1')
    {
        if (true === isset($GLOBALS['__ds__'][$alias]))
        {
            return $GLOBALS['__ds__'][$alias];
        }

        throw new InvalidArgumentException('Datasource configuration entry "'.$alias.'" cannot be found.');
    }

    /**
     * Looks up the configured database alias name by specifying its content.
     *
     * @param  array $config
     * @return string|null
     */
    public static function getDbAlias($config)
    {
        foreach ($GLOBALS['__ds__'] as $alias => $configured)
        {
            if ($config === $configured)
            {
                return $alias;
            }
        }

        return null;
    }

    /**
     * Gets application level configuration by an alias name.
     *
     * @param  string $alias Defaults to 'app1'
     * @return array
     */
    public static function getApp($alias = 'app1')
    {
        return (true === isset($GLOBALS['__profiles__'][$alias])) ? $GLOBALS['__profiles__'][$alias] : null;
    }

    /**
     * Get mail server configuration by an alias name.
     *
     * @param  string $alias Defaults to 'smpt1'
     * @return array
     */
    public static function getMail($alias = 'smpt1')
    {
        return (true === isset($GLOBALS['__mail__'][$alias])) ? $GLOBALS['__mail__'][$alias] : null;
    }
}

/**
 * Basic class provides core utilities to manage fundamental activities.
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009 (rewritten on December 31, 2009)
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class Spica
{
    /**
     * Gets Spica version, including revision number.
     *
     * @return string
     */
    public static function getVersion()
    {
        $ver   = '0.3dev';
        $svnId = '$Rev: 1869 $';
        $scId  = substr($svnId, 6);
        return $ver.' '.intval(substr($scId, 0, strlen($scId) - 2));
    }

    /**
     * Notifies that a given event that is fired at some points in the application but its accepts
     * variable parameters where this first parameter should be event name
     */
    public static function publishEvent()
    {
        $args  = func_get_args();
        $event = array_shift($args);

        // Records that event
        $GLOBALS['__state__']['fired_events'][] = $event;

        // Check if the event is registered with callbacks
        if (true === empty($GLOBALS['__event__'][$event]))
        {
            $GLOBALS['__undeclared_events__'][] = $event;
            return;
        }

        // Get callbacks
        $callbacks = $GLOBALS['__event__'][$event];

        if (true === empty($args))
        {
            for ($i = 0, $length = count($callbacks); $i < $length; $i++)
            {
                if ($callbacks[$i] instanceof SpicaInvokable)
                {
                    $callbacks[$i]->__invoke(); // PHP 5.2
                    continue;
                }

                // Executes an object of SpicaInvokable
                $callbacks[$i](); // PHP 5.3
            }

            return;
        }

        for ($i = 0, $length = count($callbacks); $i < $length; $i++)
        {
            if ($callbacks[$i] instanceof SpicaInvokable)
            {
                call_user_func_array(array($callbacks[$i], '__invoke'), $args);
                continue;
            }

            // Executes Closure
            call_user_func_array($callbacks[$i], $args); // PHP 5.3
        }
    }

    /**
     * Registers an event handler (callback, anonymous function, Closure)
     * to an application event.
     *
     * @param string   $event Event name
     * @param callback $callback
     */
    public static function subscribeEvent($event, $callback)
    {
        $GLOBALS['__event__'][$event][] = $callback;
    }

    /**
     * Sets controller forward message.
     *
     * @param SpicaActionMessage $message
     */
    public static function forwardMessage($message)
    {
        $GLOBALS['__message__']['forward'] = $message;
    }

    /**
     * Gets controller forward message.
     *
     * @return SpicaActionMessage
     */
    public static function getMessage()
    {
        return (true === isset($GLOBALS['__message__']['forward'])) ? $GLOBALS['__message__']['forward'] : null;
    }

    /**
     * Logs messages to a file.
     *
     * @param string $message
     */
    public static function log($message)
    {
        return error_log(date('Y.m.d H.i.s').' ||| '.$message."\n", 3, SpicaContext::getBasePath().'/log/message.log');
    }

    /**
     * Logs back trace to a file.
     *
     * @param string $message
     */
    public static function logTrace($message)
    {
        return error_log(date('Y.m.d H.i.s').' ||| '.$message."\n", 3, SpicaContext::getBasePath().'/log/trace.log');
    }

    /**
     * Gets full path to the configured log file.
     *
     * @return string
     */
    public static function getErrorLogFile()
    {
        if (null !== $GLOBALS['__state__']['error_log_file'])
        {
            return $GLOBALS['__state__']['error_log_file'];
        }

        $errorKey = $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['error'];
        $config   = $GLOBALS['__error__'][$errorKey];

        switch ($config['method'])
        {
            case 'date':
                $fileName = date('Y.m.d');
                break;

            case 'plain':
            default:
                $fileName = 'error';
                break;
        }

        $file = SpicaContext::getBasePath().'/'.rtrim($config['path'], '/').'/'.$config['prefix'].$fileName.$config['suffix'];
        $GLOBALS['__state__']['error_log_file'] = $file;
        return $file;
    }

    /**
     * Logs exception information to a file.
     *
     * @param Exception $e
     */
    public static function logException(Exception $e)
    {
        include_once 'library/spica/core/Debug.php';

        $caller = SpicaDebug::getCaller(1);
        $trace  = (true === isset($caller['args'][0])) ? $caller['args'][0]->getTrace() : null;

        $first = array(
            array(
                'file'     => $e->getFile(),
                'line'     => $e->getLine(),
                'function' => $trace[0]['function'],
                'class'    => isset($trace[0]['class']) ? $trace[0]['class'] : null,
                'type'     => isset($trace[0]['type']) ? $trace[0]['type'] : null,
                'args'     => isset($trace[0]['args']) ? $trace[0]['args'] : null,
        ));

        $message = $e->getMessage();
        $str = array(
            date('Y.m.d H.i.s'),
            get_class($e).'('.(!empty($message) ? '"'.$message.'"' : 'No exception message').')',
            "Trace: \n".SpicaDebug::getTraceAsString(array_merge($first, $trace)),
        );
        return error_log(implode(' ||| ', $str)."\n", 3, SpicaContext::getBasePath().'/log/exception.log');
    }

    /**
     * Logs error messages to a file.
     *
     * @param string $message
     */
    public static function logError($message)
    {
        return error_log($message."\n", 3, Spica::getErrorLogFile());
    }

    /**
     * Gets an array of errors occured in the application life cycle
     * out of the application stack.
     *
     * @return array
     */
    public static function getErrorMessages()
    {
        return (array) $GLOBALS['__state__']['errors'];
    }

    /**
     * Puts an error message into application error stack.
     *
     * @param string $message
     */
    public static function putErrorMessage($message)
    {
        $GLOBALS['__state__']['errors'][] = $message;
    }

    /**
     * Gets set error listener.
     *
     * @return SpicaErrorListener Can be null
     */
    public static function getErrorListener()
    {
        return $GLOBALS['__state__']['error_listener'];
    }

    /**
     * Registers set error listener with PHP native engine.
     *
     * @return bool
     */
    public static function listenError()
    {
        if (false === isset($GLOBALS['__state__']))
        {
            return false;
        }

        $listener = $GLOBALS['__state__']['error_listener'];

        if (null  === $listener)
        {
            $listener = new SpicaDefaultErrorListener();
            $GLOBALS['__state__']['error_listener'] = $listener;
        }

        register_shutdown_function(array($listener, 'onShutdown'));
        set_error_handler(array($listener, 'onError'));
        return true;
    }

    /**
     * Sets a custom error listener.
     *
     * @param SpicaErrorListener $listener
     */
    public static function setErrorListener($listener)
    {
        $GLOBALS['__state__']['error_listener'] = $listener;
    }
}

/**
 * An application context for a single web request to manipulate a flow execution. Allows Spica users
 * to access contextual information about the executing request, as well as the governing
 * active flow execution.
 *
 * The term 'web request' is used to describe a single call into the Spica flow system by an external
 * actor to manipulate exactly one flow execution.
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.1
 * @since      March 14, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaContext
{
    /**
     * Starts an application context.
     *
     * In Spica, a context is a web application which is a collection of PHP files and
     * content installed under a specific subset of the server's URL namespace
     * such as <code>/home/index</code>. When a context is started, its configuration
     * and custom behavior will be loaded by the Spica framework, allowing custom flows
     * and features are implemented.
     *
     * @internal
     * @param string $dir Bootstrapping directory, where SpicaContext::start() is called
     */
    public static function start($dir)
    {
        ob_start();
        // Standardize directory separator
        $dir = str_replace('\\', '/', $dir);
        // Get nominated application name in this context
        // We dont check if that application exists or or not here. init.php should do it
        $app = include $dir.'/init.php';
        set_include_path(get_include_path() . PATH_SEPARATOR . $dir.'/library' . PATH_SEPARATOR . $dir . '/apps/'.$app);

        // Load configuration
        include $dir.'/apps/'.$app.'/config/global.php';
        // Specify current bootstrapping path
        $GLOBALS['__state__']['bootstrap_path'] = $dir;
        // Specify current application base path
        $GLOBALS['__state__']['app_path']       = $dir.'/apps/'.$app;
        $GLOBALS['__state__']['app']            = $app;

        // Load custom filters and application settings.
        // Some default application configuration can be reset or changed here.
        include $dir.'/apps/'.$app.'/config/context.php';

        Spica::listenError();
        Spica::publishEvent('spica_context_start');
        if (SpicaContext::isResponseReady())
        {
            return;
        }

        // Registers the active package that is used to handle request,
        // based on user configuration in global.php
        SpicaContext::selectPackage();

        if (true === empty($GLOBALS['__state__']['profile']))
        {
            throw new RuntimeException('Configuration entry $GLOBALS[\'__state__\'][\'context\'] could not be left empty. ');
        }

        // Resolves HTTP Request into Spica's request components
        Spica::publishEvent('spica_url_resolver_start');
        if (SpicaContext::isResponseReady())
        {
            return;
        }

        $resolver = SpicaContext::getUrlResolver();

        if (null  === $resolver)
        {
            $resolver = new SpicaStandardUrlResolver(SpicaContext::getConfig(), SpicaContext::getScriptName());
            SpicaContext::setUrlResolver($resolver);
        }

        $routeName = $resolver->resolve(SpicaContext::getFullUrl());
        SpicaContext::setRoute($routeName);

        Spica::publishEvent('spica_url_resolver_stop');
        if (SpicaContext::isResponseReady())
        {
            return;
        }

        // Default behavior
        if (false === $routeName)
        {
            return SpicaResponse::sendNotFound(sprintf('The requested page "%s" is not configured in routing system. ', htmlentities(SpicaContext::getFullUrl())));
        }

        // Dispatch request to appropriate Spica components
        Spica::publishEvent('spica_dispatcher_start');
        if (SpicaContext::isResponseReady())
        {
            return;
        }

        SpicaContext::getDispatcher()->dispatch($_GET);
        Spica::publishEvent('spica_dispatcher_stop');

        $error = ob_get_contents();

        while (ob_get_level() > 0)
        {
            ob_end_clean();
        }

        if (false === empty($error))
        {
            Spica::log($error);
        }
    }

    /**
     * Stops application context. This function provides a hook to notify an event that
     * the request processing live cycle has come to an end.
     *
     * @internal
     * @see SpicaContext::start()
     * @see SpicaResponse::send()
     */
    public static function stop()
    {
        Spica::publishEvent('spica_context_stop');
    }

    /**
     * Gets an active request dispatcher.
     *
     * @return SpicaDispatcher
     */
    public static function getDispatcher()
    {
        if (null === $GLOBALS['__state__']['dispatcher'])
        {
            $GLOBALS['__state__']['dispatcher'] = new SpicaDispatcher();
        }

        return $GLOBALS['__state__']['dispatcher'];
    }

    /**
     * Sets an request dispatcher.
     *
     * @param SpicaDispatcher $dispatcher
     */
    public static function setDispatcher($dispatcher)
    {
        $GLOBALS['__state__']['dispatcher'] = $dispatcher;
    }

    /**
     * Sets the route name that matches with the current request URL.
     *
     * @internal
     * @see   SpicaContext::start()
     * @param string $name
     */
    public static function setRoute($name)
    {
        $GLOBALS['__state__']['route_name'] = $name;
    }

    /**
     * Gets the route name that matches with the current request URL.
     *
     * @return string
     */
    public static function getRoute()
    {
        return $GLOBALS['__state__']['route_name'];
    }

    /**
     * Gets the configured default module name.
     *
     * @return string|null
     */
    public static function getDefaultModule()
    {
        if (true === isset($GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['default_module']))
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['default_module'];
        }

        return null;
    }

    /**
     * Gets the configured default controller name.
     *
     * @return string|null
     */
    public static function getDefaultController()
    {
        if (true === isset($GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['default_controller']))
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['default_controller'];
        }

        return null;
    }

    /**
     * Gets the configured default action name.
     *
     * @return string|null
     */
    public static function getDefaultAction()
    {
        if (true === isset($GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['default_action']))
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['default_action'];
        }

        return null;
    }

    /**
     * Gets the context configuration.
     *
     * @return string
     */
    public static function getConfig()
    {
        return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']];
    }

    /**
     * Gets path to the active application's base directory.
     *
     * @return string
     */
    public static function getAppPath()
    {
        return $GLOBALS['__state__']['app_path'];
    }

    /**
     * Sets path to the active application's base directory.
     *
     * @param string $path
     */
    public static function setAppPath($path)
    {
        $GLOBALS['__state__']['app_path'] = $path;
    }

    /**
     * Gets application name.
     *
     * @return string
     */
    public static function getAppName()
    {
        return $GLOBALS['__state__']['app'];
    }

    /**
     * Stores application critical parameters into global application variables.
     *
     * @param string $key
     * @param string $value
     */
    public static function register($key, $value)
    {
        if ('errors' === $key || 'exceptions' === $key)
        {
            $GLOBALS['__state__'][$key] += array($value);
        }
        else
        {
            $GLOBALS['__state__'][$key] = (string) $value;
        }
    }

    /**
     * Handles context exception that is catched at the bootstrap file.
     *
     * @param Exception $e
     */
    public static function catchException($e)
    {
        include_once 'library/spica/core/utils/BaseUtils.php';

        // Continue the job in SpicaContext::start()
        // The error message should be logged in ErrorReporter
        $error = ob_get_contents();
        ob_end_clean();
        // Load exception handler hooks
        include_once SpicaContext::getAppPath().'/config/exception.php';

        $handler = SpicaContext::getExceptionHandler();

        if (null === $handler)
        {
            Spica::logException($e);
            // Should detect debug and production mode
            spica_exception_render($e);
        }
        else
        {
            $handler->process($e);
        }
    }

    /**
     * Enables or disables default final exception handling mode.
     *
     * @param SpicaExceptionHandler $handler
     */
    public static function setExceptionHandler($handler)
    {
        $GLOBALS['__state__']['e_handler'] = $handler;
    }

    /**
     * Gets the set exception handler.
     *
     * @return SpicaExceptionHandler|null
     */
    public static function getExceptionHandler()
    {
        return (false === empty($GLOBALS['__state__']['e_handler'])) ? $GLOBALS['__state__']['e_handler'] : null;
    }

    /**
     * Checks if the debug mode is enabled in the current context.
     *
     * @return bool
     */
    public static function isDebug()
    {
        if (false === isset($GLOBALS['__state__']['profile']))
        {
            throw new Exception('Active profile name is not set in application state tree yet. See $GLOBALS[\'__state__\'][\'profile\'] and SpicaContext::selectPackage(); ');
        }

        if (true === isset($GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['debug']))
        {
            return (bool) $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['debug'];
        }

        return false;
    }

    /**
     * Enables or disables debug mode.
     *
     * @param bool $mode
     */
    public static function setDebug($mode)
    {
        if (false === isset($GLOBALS['__state__']['profile']))
        {
            throw new Exception('Active profile name is not set in application state tree yet. See $GLOBALS[\'__state__\'][\'profile\'] and SpicaContext::selectPackage(); ');
        }

        $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['debug'] = (bool) $mode;
    }

    /**
     * Gets set locale name (e.x: vi_VN, en_US, fr_FR ...).
     *
     * @return string|null
     */
    public static function getLocaleName()
    {
        if (true === isset($_SESSION['__locale_string']))
        {
            return $_SESSION['__locale_string'];
        }

        if (true === isset($GLOBALS['__state__']['locale_string']))
        {
            return $GLOBALS['__state__']['locale_string'];
        }

        return null;
    }

    /**
     * Sets locale name (e.x: vi_VN, en_US, fr_FR ...).
     *
     * @param string $name Locale names like vi_VN, en_US, fr_FR ...
     * @param bool   $persistent if true the locale name will be set into session for use across requests
     */
    public static function setLocaleName($name, $persistent = true)
    {
        if (true === $persistent)
        {
            $_SESSION['__locale_string'] = $name;
        }

        $GLOBALS['__state__']['locale_string'] = $name;
    }

    /**
     * Gets the current context's base request ID.
     *
     * Context's base request ID is a string based on user request URL helps identify
     * the internal request code to the application. This ID contains 4 parts:
     *
     * + package name
     * + module name
     * + controller name
     * + action name
     *
     * All are separated by a separator :
     *
     * @return bool
     */
    public static function getRequestBaseId()
    {
        return SpicaContext::getPackage().':'.SpicaRequest::getModule().':'.SpicaRequest::getController().':'.SpicaRequest::getAction();
    }

    /**
     * Gets the active form object.
     *
     * @throws RuntimeException when there is not form object registered with $GLOBALS['__state__']
     *         or no active form object of the given class exists.
     * @param  string $className
     * @return bool
     */
    public static function getForm($className = null)
    {
        if (false === isset($GLOBALS['__state__']['form_object']))
        {
            throw new RuntimeException('Non-existent form object encountered when fetching the object of '.$className);
        }

        if (null === $className)
        {
            return $GLOBALS['__state__']['form_object'];
        }

        $form = $GLOBALS['__state__']['form_object'];

        if ($form instanceof $className)
        {
            return $form;
        }

        throw new RuntimeException('No active form object of '.$className.' exists.');
    }

    /**
     * Gets theme name that is set for use in the current application context.
     *
     * @see    SpicaUnConfiguredThemeException
     * @return array
     */
    public static function getConfiguredThemeName()
    {
        if (false === empty($GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['theme']))
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['theme'];
        }

        include_once 'library/spica/core/Exception.php';
        throw new SpicaUnConfiguredThemeException('A theme name are not configured for the application context named "'.$GLOBALS['__state__']['profile'].'"');
    }

    /**
     * Gets an object of <code>SpicaPageLayout</code> and assign theme name in use
     * to the application stack.
     *
     * @param  bool $new Create new object each time or not
     * @return SpicaPageLayout
     */
    public static function getTheme($new = false)
    {
        if (false === $new && true === isset($GLOBALS['__state__']['theme_object']) && true === is_object(($GLOBALS['__state__']['theme_object'])))
        {
            Spica::publishEvent('spica_context_get_theme');
            return $GLOBALS['__state__']['theme_object'];
        }

        include_once 'library/spica/core/view/Template.php';

        $themeName = SpicaContext::getConfiguredThemeName();
        $theme     = new SpicaPageLayout('.tpl.php', SpicaContext::getThemeBasePath(), $themeName);

        $GLOBALS['__state__']['theme_object'] = $theme;
        SpicaContext::setThemeName($themeName);
        Spica::publishEvent('spica_context_get_theme');
        return $theme;
    }

    /**
     * Assigns a theme rendering support object to application stack.
     *
     * @param object $theme
     */
    public static function setTheme($theme)
    {
        // duck typing
        if (false === method_exists($theme, 'fetch'))
        {
            throw new InvalidArgumentException('The theme object which is passed to SpicaContext::setTheme() is not valid. ');
        }

        $GLOBALS['__state__']['theme_object'] = $theme;
    }

    /**
     * Sets the theme name in use in the current application context.
     *
     * @param string $name
     */
    public static function setThemeName($name)
    {
        $GLOBALS['__state__']['theme_name'] = $name;
    }

    /**
     * Gets the theme name in use in the current application context.
     *
     * @return string|null
     */
    public static function getThemeName()
    {
        return $GLOBALS['__state__']['theme_name'];
    }

    /**
     * Gets an object of <code>SpicaTemplate</code> and assign theme name in use to
     * the application stack.
     *
     * @param  bool   $new Creates a new object or reuse the already created object.
     * @param  string $ext Template file extension. E.x: .tpl.php. Defaults to .tpl.php
     * @return SpicaTemplate
     */
    public static function getTemplate($new = false, $ext = '.tpl.php')
    {
        if (true === isset($GLOBALS['__state__']['view_object']) && is_object(($GLOBALS['__state__']['view_object'])) && false === $new)
        {
            Spica::publishEvent('spica_context_get_template');
            return $GLOBALS['__state__']['view_object'];
        }

        include_once 'library/spica/core/view/Template.php';
        $themeName = SpicaContext::getTheme();
        $view      = new SpicaTemplate($ext, SpicaContext::getAppPath().'/view/theme/'.$themeName);
        $GLOBALS['__state__']['view_object'] = $view;

        SpicaContext::setThemeName($themeName);
        Spica::publishEvent('spica_context_get_template');
        return $view;
    }

    /**
     * Assigns a template rendering support object to application stack.
     *
     * @param object $view
     */
    public static function setTemplate($view)
    {
        // duck typing
        if (false === method_exists($view, 'fetch'))
        {
            throw new InvalidArgumentException('The view object which is passed to SpicaContext::setTemplate() is not valid. ');
        }

        $GLOBALS['__state__']['view_object'] = $view;
    }

    /**
     * Gets set locale charset (e.x: UTF8, EUC-JP ...).
     *
     * @return string|null
     */
    public static function getLocaleCharset()
    {
        if (true === isset($GLOBALS['__state__']['locale_charset']))
        {
            return $GLOBALS['__state__']['locale_charset'];
        }

        return null;
    }

    /**
     * Sets locale charset (e.x: UTF8, EUC-JP ...).
     *
     * @param string $name Locale charset
     */
    public static function setLocaleCharset($name)
    {
        $GLOBALS['__state__']['locale_charset'] = $name;
    }

    /**
     * Switches to another context configuration.
     *
     * @param string $value
     */
    public static function switchProfile($name)
    {
        $GLOBALS['__state__']['profile'] = (string) $name;
    }

    /**
     * Sets a value to indicate if the response is ready to send out without continue the dispatching process.
     *
     * The value will be checked after the following events:
     *
     * 1. spica_context_start
     * 2. spica_url_resolver_start
     * 3. spica_url_resolver_stop
     * 4. spica_url_resolver_stop
     * 5. spica_dispatcher_start
     *
     * @see   SpicaContext::start()
     * @param bool $ready
     */
    public static function setResponseReady($ready = true)
    {
        $GLOBALS['__state__']['response_ready'] = (bool) $ready;
    }

    /**
     * Tests if response is ready to send out.
     * If it returns false, the dispatching process should be continue otherwise, the normal process should continue.
     *
     * The method will be called after the following event happens:
     *
     * 1. spica_context_start
     * 2. spica_url_resolver_start
     * 3. spica_url_resolver_stop
     * 4. spica_url_resolver_stop
     * 5. spica_dispatcher_start
     *
     * @see    SpicaContext::start()
     * @return bool
     */
    public static function isResponseReady()
    {
        return $GLOBALS['__state__']['response_ready'];
    }

    /**
     * Obtains the current <code>SpicaSecurityContext</code>.
     *
     * @see    SpicaContext::setSecurityContext()
     * @return SpicaSecurityContext Returns null when security context is not set
     */
    public static function getSecurityContext()
    {
        return $GLOBALS['__state__']['security_context'];
    }

    /**
     * Registers a <code>SpicaSecurityContext</code> with the application state tree.
     *
     * SecurityContext will usually be set when implementing a authentication module:
     * <code>
     *   $authenticationReq = new SpicaFormAuthenticationRequest($form, $props);
     *   $authService->authenticate($authenticationReq);
     *   SpicaSession::start();
     *   $context = new SpicaSecurityContext($formAuthService->getUserDetails());
     *   SpicaContext::setSecurityContext($context);
     * </code>
     *
     * @param SpicaSecurityContext $context
     */
    public static function setSecurityContext($context)
    {
        $GLOBALS['__state__']['security_context'] = $context;
    }

    /**
     * Sets the active form object.
     *
     * @param SpicaForm $object
     */
    public static function registerForm($object)
    {
        $GLOBALS['__state__']['form_object'] = $object;
    }

    /**
     * Creates a form object and register it with application state.
     *
     * @param  string $class
     * @param  bool   $updateContext Defaults to false
     * @return SpicaForm
     */
    public static function createForm($class, $updateContext = false)
    {
        spica_import_form($class);
        $form = new $class($updateContext);
        SpicaContext::registerForm($form);
        return $form;
    }

    /**
     * Gets backup route when the request route path can not be resolved.
     *
     * @see    SpicaUnresolvableRequestException
     * @return array
     */
    public static function getBackupRoute()
    {
        if (true === isset($GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['backup_route']))
        {
            return (array) $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['backup_route'];
        }

        return array();
    }

    /**
     * Gets the base directory path to the bootstrap file.
     *
     * @return string
     */
    public static function getBasePath()
    {
        return $GLOBALS['__state__']['bootstrap_path'];
    }

    /**
     * Gets the base path to image files in this context.
     *
     * @return string
     */
    public static function getImagePath()
    {
        if (null !== $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['image_base_path'])
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['image_base_path'];
        }

        return 'public/theme/'.SpicaContext::getAppName().'/'.SpicaContext::getThemeName().'/image';
    }

    /**
     * Gets the base path to javascript files in this context.
     *
     * @return string
     */
    public static function getJsPath()
    {
        if (null !== $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['js_base_path'])
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['js_base_path'];
        }

        return 'public/theme/'.SpicaContext::getAppName().'/'.SpicaContext::getThemeName().'/js';
    }

    /**
     * Gets the base path to CSS files in this context.
     *
     * @return string
     */
    public static function getCssPath()
    {
        if (null !== $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['css_base_path'])
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['css_base_path'];
        }

        return 'public/theme/'.SpicaContext::getAppName().'/'.SpicaContext::getThemeName().'/css';
    }

    /**
     * Gets the base path to flash files (.swf) in this context.
     *
     * @return string
     */
    public static function getFlashPath()
    {
        if (null !== $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['flash_base_path'])
        {
            return $GLOBALS['__profiles__'][$GLOBALS['__state__']['profile']]['flash_base_path'];
        }

        return 'public/theme/'.SpicaContext::getAppName().'/'.SpicaContext::getThemeName().'/flash';
    }

    /**
     * Sets the base directory path to the bootstrap file.
     *
     * @param string $path
     */
    public static function setBasePath($path)
    {
        $GLOBALS['__state__']['bootstrap_path'] = $path;
    }

    /**
     * Determines which packages should be activated and registers that packages
     * with application configuration.
     *
     * @throws RuntimeException
     */
    public static function selectPackage()
    {
        if (true === empty($GLOBALS['__state__']['profile']))
        {
            throw new RuntimeException('Unable to resolve which package should be active. Configuration entry $GLOBALS[\'__state__\'][\'context\'] could not be left empty. Please re-check your config file.');
        }

        $bootprofile = $GLOBALS['__state__']['profile'];

        if (true === empty($GLOBALS['__profiles__'][$bootprofile]['default_package']))
        {
            throw new RuntimeException('Unable to resolve which package should be active. Entry "default_package" in $GLOBALS[\'__profiles__\'][\''.$bootprofile.'\'] could not be left empty. Please re-check your config file.');
        }

        $GLOBALS['__state__']['active_package'] = $GLOBALS['__profiles__'][$bootprofile]['default_package'];
        $GLOBALS['__state__']['locale_string']  = $GLOBALS['__profiles__'][$bootprofile]['locale_string'];
        $GLOBALS['__state__']['locale_charset'] = $GLOBALS['__profiles__'][$bootprofile]['locale_charset'];
    }

    /**
     * Gets the current context's application package name.
     *
     * @return string|null
     */
    public static function getPackage()
    {
        return (false === empty($GLOBALS['__state__']['active_package'])) ? $GLOBALS['__state__']['active_package'] : null;
    }

    /**
     * Sets the current context's application package to a particular name.
     *
     * @param string $value
     */
    public static function setPackage($value)
    {
        $GLOBALS['__state__']['active_package'] = (string) $value;
    }

    /**
     * Gets the base path to the directory in which separate directories of each
     * theme is located.
     *
     * @return string
     */
    public static function getThemeBasePath()
    {
        return SpicaContext::getAppPath().'/view/theme';
    }

    /**
     * Gets the base directory path to current theme directory.
     *
     * @return string
     */
    public static function getThemeCurrentPath()
    {
        return SpicaContext::getAppPath().'/view/theme/'.SpicaContext::getThemeName();
    }

    /**
     * Gets the base path to the directory in which separate directories of application
     * localized files is located.
     *
     * @return string
     */
    public static function getLocaleBasePath()
    {
        return SpicaContext::getAppPath().'/locale';
    }

    /**
     * Gets sub-domain string in the request URL.
     *
     * www is not considered a subdomain.
     *
     * Only works for domain type:
     * 1. subdomain.domain.root. E.x: sub1.phpvietnam.net
     * 2. subdomain.domain.root.country. E.x: sub2.phpvietnam.net.vn
     *
     * @throws RuntimeException when $_SERVER['HTTP_HOST'] is not set
     * @return string
     */
    public static function getSubdomain()
    {
        // Behind proxy
        if (isset($_SERVER['HTTP_X_FORWARDED_SERVER']))
        {
            $host = $_SERVER['HTTP_X_FORWARDED_SERVER'];
        }
        elseif (isset($_SERVER['HTTP_HOST']))
        {
            $host = $_SERVER['HTTP_HOST'];
        }
        else
        {
            throw new RuntimeException('Server-side environment value HTTP_HOST or HTTP_X_FORWARDED_HOST is not set. Invalid configuration.');
        }

        $parts = explode('.', $host);

        if (3 <= count($parts) && $parts[0] != 'www')
        {
            // make sure that a subdomain is called
            return $parts[0];
        }

        return null;
    }

    /**
     * Registers URL resolver with the current context.
     *
     * @param SpicaPathResolver $resolver
     */
    public static function setUrlResolver($resolver)
    {
        $GLOBALS['__state__']['url_resolver'] = $resolver;
    }

    /**
     * Gets URL resolver that has been set before.
     *
     * @return SpicaPathResolver|null
     */
    public static function getUrlResolver()
    {
        return $GLOBALS['__state__']['url_resolver'];
    }

    /**
     * Gets full base URL in the request.
     *
     * @param  bool $hostOnly URL includes host name but excludes the path to page (including sub-directories)
     * @return string
     */
    public static function getBaseUrl($hostOnly = true)
    {
        $s    = (true  === isset($_SERVER['HTTPS']) && 'on' === $_SERVER['HTTPS']) ? 's' : '';
        $res  = 'http'.$s.'://';
        $host = getenv('HTTP_HOST');

        if ('' === $host || null === $host)
        {
            $host = $_SERVER['SERVER_NAME'];
        }

        // Fixed command line mode
        if (true === isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != '80')
        {
            $res .= $host.':'.$_SERVER['SERVER_PORT'];
        }
        else
        {
            $res .= $host;
        }

        return (true === $hostOnly) ? $res : $res.substr($_SERVER['SCRIPT_NAME'], 0, -9);
    }

    /**
     * Gets full qualified URL in the request
     *
     * @return string
     */
    public static function getFullUrl()
    {
        // URL excludes path to page
        $res = SpicaContext::getBaseUrl(true);

        if (true === isset($_SERVER['REQUEST_URI']))
        {
            $res .= $_SERVER['REQUEST_URI'];
        }
        else
        {
            $res .= $_SERVER['SCRIPT_NAME'];

            if (true === isset($_SERVER['QUERY_STRING']) && '' !== trim($_SERVER['QUERY_STRING']))
            {
                $res .= '?'.$_SERVER['QUERY_STRING'];
            }
        }

        return $res;
    }

    /**
     * Gets domain string in the request URL.
     *
     * Only works for domain type:
     * 1. subdomain.domain.root. E.x: sub1.phpvietnam.net
     * 2. subdomain.domain.root.country. E.x: sub2.phpvietnam.net.vn
     *
     * @param bool $subdomainIncluded Include subdomain or not
     * @return string
     */
    public static function getDomain($subdomainIncluded = true)
    {
        // Behind proxy
        if (isset($_SERVER['HTTP_X_FORWARDED_SERVER']))
        {
            $host = $_SERVER['HTTP_X_FORWARDED_SERVER'];
        }
        elseif (isset($_SERVER['HTTP_HOST']))
        {
            $host = $_SERVER['HTTP_HOST'];
        }
        else
        {
            throw new RuntimeException('Server-side environment value HTTP_HOST or HTTP_X_FORWARDED_HOST is not set. Invalid configuration.');
        }

        $host = parse_url($host, PHP_URL_HOST);
        return (true === $subdomainIncluded) ? $host : substr($host, strpos($host, '.') + 1);
    }

    /**
     * Builds a request URL. {@link http_build_query} is not usable in this case.
     *
     * @param  array $spec The array contains the following keys: module, controller, action,
     *         param keys and special key: # to indicate base URL
     * @return string
     */
    public static function buildUrl($spec)
    {
        if (false === is_array($spec))
        {
            throw new InvalidArgumentException('The argument $spec must be an array.');
        }

        if (false === isset($spec['module']))
        {
            $spec['module'] = SpicaRequest::getModule();
        }

        if (false === isset($spec['controller']))
        {
            $spec['controller'] = SpicaRequest::getController();
        }

        if (false === isset($spec['action']))
        {
            $spec['action'] = SpicaRequest::getAction();
        }

        $url = '';

        if (false === isset($spec['#']))
        {
            $url = SpicaContext::getBaseUrl();
        }

        unset($spec['#']);

        foreach ($spec as $key => $val)
        {
            $url .= $key.'/'.$val.'/';
        }

        return $url;
    }

    /**
     * Returns full URL of current page.
     *
     * Running PHP as a CGI program will change the value of $_SERVER['SCRIPT_NAME'].
     * $_SERVER['SCRIPT_NAME'] returns the cgi path rather than the required file.
     * Please set 'cgi.fix_pathinfo=1' in the php.ini.
     *
     * Also, $_SERVER['SCRIPT_URL'] and $_SERVER['SCRIPT_NAME'] is not set
     * if you use lighttpd and php fcgi
     *
     * @return string
     */
    public static function getScriptName()
    {
        if (PHP_SAPI === 'cgi' && isset($_ENV['SCRIPT_URL']))
        {
            $key = 'SCRIPT_URL';
        }
        else
        {
            $key = 'SCRIPT_NAME';
        }

        if (true === isset($_SERVER[$key]))
        {
            return $_SERVER[$key];
        }

        if (true === isset($_ENV[$key]))
        {
            return $_ENV[$key];
        }

        if (false !== getenv($key))
        {
            return getenv($key);
        }

        return $_SERVER['REQUEST_URI'].'/';
    }
}

/**
 * Base class for Spica exceptions.
 *
 * namespace spica\core
 *
 * @category   spica
 * @package    core
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.3
 * @since      March 26, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: Base.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaException extends Exception {}

/**
 * Gets the base directory path to class files that represent page blocks.
 *
 * @return string
 */
function spica_block_classpath()
{
    return SpicaContext::getAppPath().'/block';
}

/**
 * Gets the base directory path to class files that represent page block groups.
 *
 * @return string
 */
function spica_blockgroup_classpath()
{
    return SpicaContext::getAppPath().'/blockgroup';
}

/**
 * Gets the base directory path to class files that represent UI widgets.
 *
 * @return string
 */
function spica_widget_classpath()
{
    return SpicaContext::getAppPath().'/ui';
}

/**
 * Includes a user-land form class into the current scope.
 * This function is for convenience purpose.
 *
 * @param string $class Class name
 */
function spica_import_form($class)
{
    include_once SpicaContext::getAppPath().'/form/'.$class.'.php';
}

/**
 * Includes a domain object class into the current scope.
 * This function is for convenience purpose.
 *
 * @param string $class Class name
 */
function spica_import_model($class)
{
    include_once SpicaContext::getAppPath().'/model/'.$class.'.php';
}

/**
 * Includes a service class (database service, authentication service ...)
 * into the current scope. This function is for convenience purpose.
 *
 * @param string $class Class name
 */
function spica_import_service($class)
{
    include_once SpicaContext::getAppPath().'/service/'.$class.'.php';
}

/**
 * Includes a user-land lib class into the current scope.
 * This function is for convenience purpose.
 *
 * @param string $class Class name
 */
function spica_import_lib($class)
{
    include_once SpicaContext::getAppPath().'/lib/'.$class.'.php';
}

/**
 * Includes a user-land helper class into the current scope.
 * This function is for convenience purpose.
 *
 * @param string $class Class name
 */
function spica_import_helper($class)
{
    include_once SpicaContext::getAppPath().'/helper/'.$class.'.php';
}

/**
 * Includes a user-land event listener class into the current scope.
 * This function is for convenience purpose.
 *
 * @param string $class Class name
 */
function spica_import_event($class)
{
    include_once SpicaContext::getAppPath().'/event/'.$class.'.php';
}

/**
 * Censors full path of a file.
 *
 * @param  string $fullPath
 * @return string
 */
function spica_path_make_relative($fullPath)
{
    $dir = dirname($fullPath);
    return basename(dirname($dir)).'/'.basename($dir).'/'.basename($fullPath);
}

/**
 * Encodes URL (dot notation will be replaced with %2E).
 *
 * @see    5 Solutions to Url Encoded Slashes (%2F) Problem in Apache (Page Not Found encountered)
 *         {@link http://www.jampmark.com/web-scripting/5-solutions-to-url-encoded-slashes-problem-in-apache.html}
 * @param  string $url
 * @return string
 */
function spica_url_encode($url)
{
    return str_replace(array('%2F', '%5C', '.'), array('%252F', '%255C', '%252E'), urlencode($url));
}

/**
 * Decodes URL (%2E will be replaced with dot notation).
 *
 * @param  string $url
 * @return string
 */
function spica_url_decode($url)
{
    return urldecode(str_replace(array('%252F', '%255C', '%252E'), array('%2F', '%5C', '.'), $url));
}

/**
 * Converts current local time to GMT.
 * 
 * @param string $now
 * @return string
 */
function spica_to_gmt($now = null)
{
    return gmdate('D, d M Y H:i:s',  ($now == null) ? time() : $now);
}

/**
 * Looks up and returns the localized message for the specified key, using active locale
 * in the context.
 *
 * Message key includes 3 parts
 *
 * 0: Directory name
 * 1: File name
 * 2: Array key
 *
 * @param  string $key message key
 * @return string the localized message for the specified key or an error message if no such message exists
 */
function __k($key)
{
    if (true === isset($GLOBALS['__t__'][$key]))
    {
        return $GLOBALS['__t__'][$key];
    }

    $parts = explode('.', $key, 3);

    if (false === isset($parts[2]))
    {
        return $key.': Locale key MUST includes 3 parts. (Error L01)';
    }

    $cs = SpicaContext::getLocaleCharset();

    if ('UTF8' === $cs)
    {
        $ext = '.php';
    }
    else
    {
        $ext = '.'.$cs.'.php';
    }

    // Key name space
    $ns   = $parts[0].$parts[1];
    $path = SpicaContext::getLocaleBasePath().'/'.SpicaContext::getLocaleName().'/'.$parts[0].'/'.$parts[1].$ext;
    $keys = include $path;

    foreach ($keys as $k => $v)
    {
        $GLOBALS['__t__'][$ns.'.'.$k] = $v;
    }

    if (true === isset($keys[$parts[2]]))
    {
        return $keys[$parts[2]];
    }

    Spica::logError($key.': Locale key is not defined in '.$path);
    return $key.': Locale key not defined. (Error L02)';
}

/**
 * Looks up and returns the localized message for the specified key and locale.
 *
 * Message key includes 3 parts
 *
 * 0: Directory name
 * 1: File name
 * 2: Array key
 *
 * @param  string $key message key
 * @param  string $locale The locale to use for this message.
 * @return string the localized message for the specified key or an error message if no such message exists
 */
function __kc($key, $locale)
{
    if (true === isset($GLOBALS['__tl__'][$locale][$key]))
    {
        return $GLOBALS['__tl__'][$locale][$key];
    }

    $parts = explode('.', $key, 3);

    if (false === isset($parts[2]))
    {
        return $key.': Locale key MUST includes 3 parts. (Error L01)';
    }

    $cs = SpicaContext::getLocaleCharset();

    if ('UTF8' === $cs)
    {
        $ext = '.php';
    }
    else
    {
        $ext = '.'.$cs.'.php';
    }

    // Key name space
    $ns   = $parts[0].$parts[1];
    $path = SpicaContext::getLocaleBasePath().'/'.$locale.'/'.$parts[0].'/'.$parts[1].$ext;
    $keys = include $path;

    foreach ($keys as $k => $v)
    {
        $GLOBALS['__tl__'][$locale][$ns.'.'.$k] = $v;
    }

    if (true === isset($keys[$parts[2]]))
    {
        return $keys[$parts[2]];
    }

    Spica::logError($key.': Locale key is not defined in '.$path);
    return $key.': Locale key not defined. (Error L02)';
}

?>